Separate analysis and diagnosis components for pointer nullability verification.
- `pointer_nullability_analysis.h/cc`: accummulate nullability information of pointers.
- `pointer_nullability_diagnosis.h/cc`: checks null safety (e.g. if a dereference is safe).
- `pointer_nullability.h/cc`: common functions for updating a PointerValue's nullability.
PiperOrigin-RevId: 464823057
diff --git a/nullability_verification/BUILD b/nullability_verification/BUILD
index a3aa604..bf12ade 100644
--- a/nullability_verification/BUILD
+++ b/nullability_verification/BUILD
@@ -24,6 +24,7 @@
srcs = ["pointer_nullability_analysis.cc"],
hdrs = ["pointer_nullability_analysis.h"],
deps = [
+ ":pointer_nullability",
":pointer_nullability_lattice",
":pointer_nullability_matchers",
"//common:check",
@@ -35,11 +36,38 @@
],
)
+cc_library(
+ name = "pointer_nullability_diagnosis",
+ srcs = ["pointer_nullability_diagnosis.cc"],
+ hdrs = ["pointer_nullability_diagnosis.h"],
+ deps = [
+ ":pointer_nullability",
+ ":pointer_nullability_matchers",
+ "@llvm-project//clang:analysis",
+ "@llvm-project//clang:ast",
+ "@llvm-project//clang:ast_matchers",
+ "@llvm-project//llvm:Support",
+ ],
+)
+
+cc_library(
+ name = "pointer_nullability",
+ srcs = ["pointer_nullability.cc"],
+ hdrs = ["pointer_nullability.h"],
+ deps = [
+ "@llvm-project//clang:analysis",
+ "@llvm-project//clang:ast",
+ "@llvm-project//llvm:Support",
+ ],
+)
+
cc_test(
- name = "pointer_nullability_analysis_test",
- srcs = ["pointer_nullability_analysis_test.cc"],
+ name = "pointer_nullability_verification_test",
+ srcs = ["pointer_nullability_verification_test.cc"],
deps = [
":pointer_nullability_analysis",
+ ":pointer_nullability_diagnosis",
+ "@llvm-project//clang:basic",
"@llvm-project//clang/unittests:dataflow_testing_support",
"@llvm-project//llvm:Support",
"@llvm-project//llvm:TestingSupport",
diff --git a/nullability_verification/pointer_nullability.cc b/nullability_verification/pointer_nullability.cc
new file mode 100644
index 0000000..ffd81d5
--- /dev/null
+++ b/nullability_verification/pointer_nullability.cc
@@ -0,0 +1,56 @@
+// 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_verification/pointer_nullability.h"
+
+#include "clang/Analysis/FlowSensitive/Value.h"
+#include "llvm/ADT/StringRef.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+
+using dataflow::AtomicBoolValue;
+using dataflow::BoolValue;
+using dataflow::Environment;
+using dataflow::PointerValue;
+using dataflow::SkipPast;
+
+/// 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 `NotNull`.
+constexpr llvm::StringLiteral kKnown = "is_known";
+constexpr llvm::StringLiteral kNotNull = "is_notnull";
+
+std::pair<AtomicBoolValue&, AtomicBoolValue&> getPointerNullState(
+ const Expr* PointerExpr, const Environment& Env) {
+ auto* PointerVal =
+ cast<PointerValue>(Env.getValue(*PointerExpr, SkipPast::Reference));
+ auto& PointerKnown = *cast<AtomicBoolValue>(PointerVal->getProperty(kKnown));
+ auto& PointerNotNull =
+ *cast<AtomicBoolValue>(PointerVal->getProperty(kNotNull));
+ return {PointerKnown, PointerNotNull};
+}
+
+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(const Expr* PointerExpr, Environment& Env,
+ BoolValue* KnownConstraint,
+ BoolValue* NotNullConstraint) {
+ if (auto* PointerVal = cast_or_null<PointerValue>(
+ Env.getValue(*PointerExpr, SkipPast::Reference))) {
+ initPointerBoolProperty(*PointerVal, kKnown, KnownConstraint, Env);
+ initPointerBoolProperty(*PointerVal, kNotNull, NotNullConstraint, Env);
+ }
+}
+
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
\ No newline at end of file
diff --git a/nullability_verification/pointer_nullability.h b/nullability_verification/pointer_nullability.h
new file mode 100644
index 0000000..96444b0
--- /dev/null
+++ b/nullability_verification/pointer_nullability.h
@@ -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
+
+#ifndef CRUBIT_NULLABILITY_VERIFICATION_POINTER_NULLABILITY_H_
+#define CRUBIT_NULLABILITY_VERIFICATION_POINTER_NULLABILITY_H_
+
+#include <utility>
+
+#include "clang/AST/Expr.h"
+#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
+#include "clang/Analysis/FlowSensitive/Value.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+
+/// 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 not null.
+std::pair<dataflow::AtomicBoolValue&, dataflow::AtomicBoolValue&>
+getPointerNullState(const Expr* PointerExpr, const dataflow::Environment& Env);
+
+/// Sets the nullness properties on the PointerValue assigned to `PointerExpr`
+/// if not already initialised.
+///
+/// The boolean properties may be constrained by passing in the BoolValues
+/// representing true or false to `KnownConstraint` and `NotNullConstraint`.
+/// Otherwise, the properties are set to freshly created atomic booleans.
+void initPointerNullState(const Expr* PointerExpr, dataflow::Environment& Env,
+ dataflow::BoolValue* KnownConstraint,
+ dataflow::BoolValue* NotNullConstraint = nullptr);
+
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
+
+#endif // CRUBIT_NULLABILITY_VERIFICATION_POINTER_NULLABILITY_H_
diff --git a/nullability_verification/pointer_nullability_analysis.cc b/nullability_verification/pointer_nullability_analysis.cc
index c098111..11b8efd 100644
--- a/nullability_verification/pointer_nullability_analysis.cc
+++ b/nullability_verification/pointer_nullability_analysis.cc
@@ -7,6 +7,7 @@
#include <string>
#include "common/check.h"
+#include "nullability_verification/pointer_nullability.h"
#include "nullability_verification/pointer_nullability_lattice.h"
#include "nullability_verification/pointer_nullability_matchers.h"
#include "clang/AST/ASTContext.h"
@@ -25,55 +26,19 @@
namespace nullability {
using ast_matchers::MatchFinder;
-using dataflow::AtomicBoolValue;
using dataflow::BoolValue;
using dataflow::Environment;
using dataflow::MatchSwitchBuilder;
-using dataflow::PointerValue;
using dataflow::SkipPast;
using dataflow::TransferState;
using dataflow::Value;
namespace {
-constexpr llvm::StringLiteral kKnown = "is_known";
-constexpr llvm::StringLiteral kNotNull = "is_notnull";
-
-std::pair<AtomicBoolValue&, AtomicBoolValue&> getPointerNullState(
- const Expr* PointerExpr, TransferState<PointerNullabilityLattice>& State) {
- auto* PointerVal =
- cast<PointerValue>(State.Env.getValue(*PointerExpr, SkipPast::Reference));
- auto& PointerKnown = *cast<AtomicBoolValue>(PointerVal->getProperty(kKnown));
- auto& PointerNotNull =
- *cast<AtomicBoolValue>(PointerVal->getProperty(kNotNull));
- return {PointerKnown, PointerNotNull};
-}
-
-void initPointerBoolProperty(PointerValue& PointerVal, llvm::StringRef Name,
- BoolValue* BoolVal, Environment& Env) {
- if (PointerVal.getProperty(Name) == nullptr) {
- PointerVal.setProperty(Name,
- BoolVal ? *BoolVal : Env.makeAtomicBoolValue());
- }
-}
-
-/// 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 `NotNull`.
-void initPointerNullState(const Expr* PointerExpr,
- TransferState<PointerNullabilityLattice>& State,
- BoolValue* Known, BoolValue* NotNull = nullptr) {
- if (auto* PointerVal = cast_or_null<PointerValue>(
- State.Env.getValue(*PointerExpr, SkipPast::Reference))) {
- initPointerBoolProperty(*PointerVal, kKnown, Known, State.Env);
- initPointerBoolProperty(*PointerVal, kNotNull, NotNull, State.Env);
- }
-}
-
void transferInitNotNullPointer(
const Expr* NotNullPointer, const MatchFinder::MatchResult&,
TransferState<PointerNullabilityLattice>& State) {
- initPointerNullState(NotNullPointer, State,
+ initPointerNullState(NotNullPointer, State.Env,
/*Known=*/&State.Env.getBoolLiteralValue(true),
/*NotNull=*/&State.Env.getBoolLiteralValue(true));
}
@@ -81,7 +46,7 @@
void transferInitNullPointer(const Expr* NullPointer,
const MatchFinder::MatchResult&,
TransferState<PointerNullabilityLattice>& State) {
- initPointerNullState(NullPointer, State,
+ initPointerNullState(NullPointer, State.Env,
/*Known=*/&State.Env.getBoolLiteralValue(true),
/*NotNull=*/&State.Env.getBoolLiteralValue(false));
}
@@ -89,50 +54,21 @@
void transferInitNullablePointer(
const Expr* NullablePointer,
TransferState<PointerNullabilityLattice>& State) {
- initPointerNullState(NullablePointer, State,
+ initPointerNullState(NullablePointer, State.Env,
/*Known=*/&State.Env.getBoolLiteralValue(true));
}
void transferInitPointerFromDecl(
const Expr* PointerExpr, const MatchFinder::MatchResult&,
TransferState<PointerNullabilityLattice>& State) {
- // TODO(wyt): Implement processing of nullability annotations. The current
- // implementation treats unnannotated pointers as nullable.
+ // TODO(b/233582219): Implement processing of nullability annotations. The
+ // current implementation treats unnannotated pointers as nullable.
transferInitNullablePointer(PointerExpr, State);
}
-void transferPointerAccess(const Expr* PointerExpr,
- TransferState<PointerNullabilityLattice>& State) {
- auto [PointerKnown, PointerNotNull] = getPointerNullState(PointerExpr, State);
- auto& PointerNotKnownNull = State.Env.makeNot(
- State.Env.makeAnd(PointerKnown, State.Env.makeNot(PointerNotNull)));
- if (!State.Env.flowConditionImplies(PointerNotKnownNull)) {
- State.Lattice.addViolation(PointerExpr);
- }
-}
-
-void transferDereference(const UnaryOperator* UnaryOp,
- const MatchFinder::MatchResult&,
- TransferState<PointerNullabilityLattice>& State) {
- transferPointerAccess(UnaryOp->getSubExpr(), State);
-}
-
-void transferMemberExprInvolvingPointers(
- const MemberExpr* MemberExpr, const MatchFinder::MatchResult& Result,
- TransferState<PointerNullabilityLattice>& State) {
- if (MemberExpr->isArrow()) {
- // Base expr is a pointer, check that (->) access is safe
- transferPointerAccess(MemberExpr->getBase(), State);
- }
- if (MemberExpr->getType()->isAnyPointerType()) {
- // Accessed member is a pointer, initialise its nullability
- transferInitPointerFromDecl(MemberExpr, Result, State);
- }
-}
-
-// TODO(wyt): 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.
+// 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 transferNullCheckComparison(
const BinaryOperator* BinaryOp, const MatchFinder::MatchResult& result,
TransferState<PointerNullabilityLattice>& State) {
@@ -149,8 +85,10 @@
? State.Env.makeNot(PointerComparison)
: PointerComparison;
- auto [LHSKnown, LHSNotNull] = getPointerNullState(BinaryOp->getLHS(), State);
- auto [RHSKnown, RHSNotNull] = getPointerNullState(BinaryOp->getRHS(), State);
+ auto [LHSKnown, LHSNotNull] =
+ getPointerNullState(BinaryOp->getLHS(), State.Env);
+ auto [RHSKnown, RHSNotNull] =
+ getPointerNullState(BinaryOp->getRHS(), State.Env);
auto& LHSKnownNotNull = State.Env.makeAnd(LHSKnown, LHSNotNull);
auto& RHSKnownNotNull = State.Env.makeAnd(RHSKnown, RHSNotNull);
auto& LHSKnownNull =
@@ -173,7 +111,7 @@
const Expr* CastExpr, const MatchFinder::MatchResult&,
TransferState<PointerNullabilityLattice>& State) {
auto [PointerKnown, PointerNotNull] =
- getPointerNullState(CastExpr->IgnoreImplicit(), State);
+ getPointerNullState(CastExpr->IgnoreImplicit(), State.Env);
auto& CastExprLoc = State.Env.createStorageLocation(*CastExpr);
State.Env.setValue(CastExprLoc, PointerNotNull);
State.Env.setStorageLocation(*CastExpr, CastExprLoc);
@@ -186,12 +124,7 @@
.CaseOf<Expr>(isCXXThisExpr(), transferInitNotNullPointer)
.CaseOf<Expr>(isAddrOf(), transferInitNotNullPointer)
.CaseOf<Expr>(isNullPointerLiteral(), transferInitNullPointer)
- // Handles initialization of null states of member pointers and safety of
- // member access (->) on pointers
- .CaseOf<MemberExpr>(isMemberExprInvolvingPointers(),
- transferMemberExprInvolvingPointers)
- // Handles pointer dereferencing (*ptr)
- .CaseOf<UnaryOperator>(isPointerDereference(), transferDereference)
+ .CaseOf<MemberExpr>(isMemberOfPointerType(), transferInitPointerFromDecl)
// Handles comparison between 2 pointers
.CaseOf<BinaryOperator>(isPointerCheckBinOp(),
transferNullCheckComparison)
diff --git a/nullability_verification/pointer_nullability_analysis.h b/nullability_verification/pointer_nullability_analysis.h
index f15c358..30ea8f7 100644
--- a/nullability_verification/pointer_nullability_analysis.h
+++ b/nullability_verification/pointer_nullability_analysis.h
@@ -26,6 +26,8 @@
// TODO(b/233582219): Implement tracking of nullability to distinguish
// safe/unsafe accesses
+/// Analyses constructs in the source code to collect nullability information
+/// about pointers at each program point.
class PointerNullabilityAnalysis
: public dataflow::DataflowAnalysis<PointerNullabilityAnalysis,
PointerNullabilityLattice> {
diff --git a/nullability_verification/pointer_nullability_analysis_test.cc b/nullability_verification/pointer_nullability_analysis_test.cc
deleted file mode 100644
index 77f64bf..0000000
--- a/nullability_verification/pointer_nullability_analysis_test.cc
+++ /dev/null
@@ -1,638 +0,0 @@
-// 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_verification/pointer_nullability_analysis.h"
-
-#include <string>
-
-#include "third_party/llvm/llvm-project/clang/unittests/Analysis/FlowSensitive/TestingSupport.h"
-#include "llvm/ADT/ArrayRef.h"
-#include "llvm/ADT/StringRef.h"
-#include "llvm/Support/Error.h"
-#include "llvm/Testing/Support/Error.h"
-#include "third_party/llvm/llvm-project/llvm/utils/unittest/googletest/include/gtest/gtest.h"
-
-namespace clang {
-namespace tidy {
-namespace nullability {
-namespace {
-
-using ::testing::Pair;
-using ::testing::Test;
-using ::testing::UnorderedElementsAre;
-
-using dataflow::DataflowAnalysisState;
-using dataflow::Environment;
-using dataflow::test::checkDataflow;
-
-MATCHER(IsSafe, "") { return arg.Lattice.isSafe(); }
-MATCHER(IsUnsafe, "") { return !arg.Lattice.isSafe(); }
-
-class PointerNullabilityTest : public Test {
- protected:
- template <typename Matcher>
- void expectDataflow(llvm::StringRef Code, Matcher Expectations) {
- ASSERT_THAT_ERROR(
- checkDataflow<PointerNullabilityAnalysis>(
- Code, "target",
- [](ASTContext &ASTCtx, Environment &) {
- return PointerNullabilityAnalysis(ASTCtx);
- },
- [&Expectations](
- llvm::ArrayRef<
- std::pair<std::string,
- DataflowAnalysisState<PointerNullabilityLattice>>>
- Results,
- ASTContext &) { EXPECT_THAT(Results, Expectations); },
- {"-fsyntax-only", "-std=c++17", "-Wno-unused-value"}),
- llvm::Succeeded());
- }
-};
-
-TEST_F(PointerNullabilityTest, NoPointerOperations) {
- std::string Code = R"(
- void target() {
- 1 + 2;
- /*[[safe]]*/
- }
- )";
- expectDataflow(Code, UnorderedElementsAre(Pair("safe", IsSafe())));
-}
-
-TEST_F(PointerNullabilityTest, DereferenceWithoutACheck) {
- std::string Code = R"(
- void target(int* maybeNull) {
- *maybeNull;
- /*[[unsafe]]*/
- }
- )";
- expectDataflow(Code, UnorderedElementsAre(Pair("unsafe", IsUnsafe())));
-}
-
-TEST_F(PointerNullabilityTest, InitializedWithNullPtrLiteral) {
- std::string NullPtr = R"(
- void target() {
- int *null = nullptr;
- *null;
- /*[[unsafe]]*/
- }
- )";
- expectDataflow(NullPtr, UnorderedElementsAre(Pair("unsafe", IsUnsafe())));
-
- std::string ZeroAsNull = R"(
- void target() {
- int *null = 0;
- *null;
- /*[[unsafe]]*/
- }
- )";
- expectDataflow(ZeroAsNull, UnorderedElementsAre(Pair("unsafe", IsUnsafe())));
-}
-
-TEST_F(PointerNullabilityTest, InitializedWithAddressOf) {
- std::string Code = R"(
- void target(int x) {
- int *nonNull = &x;
- *nonNull;
- /*[[safe]]*/
- }
- )";
- expectDataflow(Code, UnorderedElementsAre(Pair("safe", IsSafe())));
-}
-
-TEST_F(PointerNullabilityTest, InitializedWithOtherPointer) {
- std::string DerefCopyOfNonNull = R"(
- void target(int x) {
- int *nonNull = &x;
- int *nonNullCopy = nonNull;
- *nonNullCopy;
- /*[[safe]]*/
- }
- )";
- expectDataflow(DerefCopyOfNonNull,
- UnorderedElementsAre(Pair("safe", IsSafe())));
-
- std::string DerefCopyOfNullable = R"(
- void target(int* nullable) {
- int *nullableCopy = nullable;
- *nullableCopy;
- /*[[unsafe]]*/
- }
- )";
- expectDataflow(DerefCopyOfNullable,
- UnorderedElementsAre(Pair("unsafe", IsUnsafe())));
-
- std::string DerefCopyOfNullableCheckOriginal = R"(
- void target(int* nullable) {
- int *nullableCopy = nullable;
- if (nullable) {
- *nullableCopy;
- /*[[safe]]*/
- } else {
- *nullableCopy;
- /*[[unsafe-1]]*/
- }
- *nullableCopy;
- /*[[unsafe-2]]*/
- }
- )";
- expectDataflow(
- DerefCopyOfNullableCheckOriginal,
- UnorderedElementsAre(Pair("safe", IsSafe()), Pair("unsafe-1", IsUnsafe()),
- Pair("unsafe-2", IsUnsafe())));
-
- std::string DerefNullableCheckCopy = R"(
- void target(int* nullable) {
- int *nullableCopy = nullable;
- if (nullableCopy) {
- *nullable;
- /*[[safe]]*/
- } else {
- *nullable;
- /*[[unsafe-1]]*/
- }
- *nullable;
- /*[[unsafe-2]]*/
- }
- )";
- expectDataflow(
- DerefNullableCheckCopy,
- UnorderedElementsAre(Pair("safe", IsSafe()), Pair("unsafe-1", IsUnsafe()),
- Pair("unsafe-2", IsUnsafe())));
-}
-
-TEST_F(PointerNullabilityTest, CheckByComparisonToNullPtr) {
- std::string NENullRight = R"(
- void target(int *maybeNull) {
- if (maybeNull != nullptr) {
- *maybeNull;
- /*[[safe]]*/
- } else {
- *maybeNull;
- /*[[unsafe-1]]*/
- }
- *maybeNull;
- /*[[unsafe-2]]*/
- }
- )";
- expectDataflow(
- NENullRight,
- UnorderedElementsAre(Pair("safe", IsSafe()), Pair("unsafe-1", IsUnsafe()),
- Pair("unsafe-2", IsUnsafe())));
-
- std::string NENullLeft = R"(
- void target(int *maybeNull) {
- if (nullptr != maybeNull) {
- *maybeNull;
- /*[[safe]]*/
- } else {
- *maybeNull;
- /*[[unsafe-1]]*/
- }
- *maybeNull;
- /*[[unsafe-2]]*/
- }
- )";
- expectDataflow(
- NENullLeft,
- UnorderedElementsAre(Pair("safe", IsSafe()), Pair("unsafe-1", IsUnsafe()),
- Pair("unsafe-2", IsUnsafe())));
-
- std::string EQNullRight = R"(
- void target(int* maybeNull) {
- if (maybeNull == nullptr) {
- *maybeNull;
- /*[[unsafe-1]]*/
- } else {
- *maybeNull;
- /*[[safe]]*/
- }
- *maybeNull;
- /*[[unsafe-2]]*/
- }
- )";
- expectDataflow(
- EQNullRight,
- UnorderedElementsAre(Pair("safe", IsSafe()), Pair("unsafe-1", IsUnsafe()),
- Pair("unsafe-2", IsUnsafe())));
-
- std::string EQNullLeft = R"(
- void target(int* maybeNull) {
- if (nullptr == maybeNull) {
- *maybeNull;
- /*[[unsafe-1]]*/
- } else {
- *maybeNull;
- /*[[safe]]*/
- }
- *maybeNull;
- /*[[unsafe-2]]*/
- }
- )";
- expectDataflow(
- EQNullLeft,
- UnorderedElementsAre(Pair("safe", IsSafe()), Pair("unsafe-1", IsUnsafe()),
- Pair("unsafe-2", IsUnsafe())));
-}
-
-TEST_F(PointerNullabilityTest, CheckByImplicitCastToBool) {
- std::string PointerAsBool = R"(
- void target(int* maybeNull) {
- if (maybeNull) {
- *maybeNull;
- /*[[safe]]*/
- } else {
- *maybeNull;
- /*[[unsafe-1]]*/
- }
- *maybeNull;
- /*[[unsafe-2]]*/
- }
- )";
- expectDataflow(
- PointerAsBool,
- UnorderedElementsAre(Pair("safe", IsSafe()), Pair("unsafe-1", IsUnsafe()),
- Pair("unsafe-2", IsUnsafe())));
-
- std::string PointerAsBoolNegated = R"(
- void target(int* maybeNull) {
- if (!maybeNull) {
- *maybeNull;
- /*[[unsafe-1]]*/
- } else {
- *maybeNull;
- /*[[safe]]*/
- }
- *maybeNull;
- /*[[unsafe-2]]*/
- }
- )";
- expectDataflow(
- PointerAsBoolNegated,
- UnorderedElementsAre(Pair("safe", IsSafe()), Pair("unsafe-1", IsUnsafe()),
- Pair("unsafe-2", IsUnsafe())));
-}
-
-TEST_F(PointerNullabilityTest, CheckByComparisonToOtherNullPtr) {
- std::string NEOtherNullPtr = R"(
- void target(int* maybeNull) {
- int *null = nullptr;
- if (maybeNull != null) {
- *maybeNull;
- /*[[safe]]*/
- } else {
- *maybeNull;
- /*[[unsafe-1]]*/
- }
- *maybeNull;
- /*[[unsafe-2]]*/
- }
- )";
- expectDataflow(
- NEOtherNullPtr,
- UnorderedElementsAre(Pair("safe", IsSafe()), Pair("unsafe-1", IsUnsafe()),
- Pair("unsafe-2", IsUnsafe())));
-
- std::string EQOtherNullPtr = R"(
- void target(int* maybeNull) {
- int *null = nullptr;
- if (maybeNull == null) {
- *maybeNull;
- /*[[unsafe-1]]*/
- } else {
- *maybeNull;
- /*[[safe]]*/
- }
- *maybeNull;
- /*[[unsafe-2]]*/
- }
- )";
- expectDataflow(
- EQOtherNullPtr,
- UnorderedElementsAre(Pair("safe", IsSafe()), Pair("unsafe-1", IsUnsafe()),
- Pair("unsafe-2", IsUnsafe())));
-}
-
-TEST_F(PointerNullabilityTest, CheckByComparisonToOtherNonNullPtr) {
- std::string NEOtherNonNullPtr = R"(
- void target(int* maybeNull, int x) {
- int* nonNull = &x;
- if (maybeNull != nonNull) {
- *maybeNull;
- /*[[unsafe-1]]*/
- } else {
- *maybeNull;
- /*[[safe]]*/
- }
- *maybeNull;
- /*[[unsafe-2]]*/
- }
- )";
- expectDataflow(NEOtherNonNullPtr,
- UnorderedElementsAre(Pair("unsafe-1", IsUnsafe()),
- Pair("unsafe-2", IsUnsafe()),
- Pair("safe", IsSafe())));
-
- std::string EQOtherNonNullPtr = R"(
- void target(int* maybeNull, int x) {
- int* nonNull = &x;
- if (maybeNull == nonNull) {
- *maybeNull;
- /*[[safe]]*/
- } else {
- *maybeNull;
- /*[[unsafe-1]]*/
- }
- *maybeNull;
- /*[[unsafe-2]]*/
- }
- )";
- expectDataflow(
- EQOtherNonNullPtr,
- UnorderedElementsAre(Pair("safe", IsSafe()), Pair("unsafe-1", IsUnsafe()),
- Pair("unsafe-2", IsUnsafe())));
-}
-
-TEST_F(PointerNullabilityTest, CheckByComparisonToOtherUnknownPtr) {
- std::string NEOtherUnknownPtr = R"(
- void target(int* x, int* y) {
- if (x != y) {
- *x;
- /*[[unsafe-1]]*/
- } else {
- *x;
- /*[[unsafe-2]]*/
- }
- *x;
- /*[[unsafe-3]]*/
- }
- )";
- expectDataflow(NEOtherUnknownPtr,
- UnorderedElementsAre(Pair("unsafe-1", IsUnsafe()),
- Pair("unsafe-2", IsUnsafe()),
- Pair("unsafe-3", IsUnsafe())));
-
- std::string EQOtherUnknownPtr = R"(
- void target(int* x, int* y) {
- if (x == y) {
- *x;
- /*[[unsafe-1]]*/
- } else {
- *x;
- /*[[unsafe-2]]*/
- }
- *x;
- /*[[unsafe-3]]*/
- }
- )";
- expectDataflow(EQOtherUnknownPtr,
- UnorderedElementsAre(Pair("unsafe-1", IsUnsafe()),
- Pair("unsafe-2", IsUnsafe()),
- Pair("unsafe-3", IsUnsafe())));
-}
-
-TEST_F(PointerNullabilityTest, BinaryExpressions) {
- std::string And = R"(
- void target(int* x, int* y) {
- if (x && y) {
- *x;
- /*[[safe]]*/
- } else {
- *x;
- /*[[unsafe-1]]*/
- }
- *x;
- /*[[unsafe-2]]*/
- }
- )";
- expectDataflow(And, UnorderedElementsAre(Pair("safe", IsSafe()),
- Pair("unsafe-1", IsUnsafe()),
- Pair("unsafe-2", IsUnsafe())));
-
- std::string Or = R"(
- void target(int* x, int* y) {
- if (x || y) {
- *x;
- /*[[unsafe-1]]*/
- } else {
- *x;
- /*[[unsafe-2]]*/
- }
- *x;
- /*[[unsafe-3]]*/
- }
- )";
- expectDataflow(Or, UnorderedElementsAre(Pair("unsafe-1", IsUnsafe()),
- Pair("unsafe-2", IsUnsafe()),
- Pair("unsafe-3", IsUnsafe())));
-
- std::string AndNegatedBoth = R"(
- void target(int* x, int* y) {
- if (!x && !y) {
- *x;
- /*[[unsafe-1]]*/
- } else {
- *x;
- /*[[unsafe-2]]*/
- }
- *x;
- /*[[unsafe-3]]*/
- }
- )";
- expectDataflow(AndNegatedBoth,
- UnorderedElementsAre(Pair("unsafe-1", IsUnsafe()),
- Pair("unsafe-2", IsUnsafe()),
- Pair("unsafe-3", IsUnsafe())));
-
- std::string OrNegatedBoth = R"(
- void target(int* x, int* y) {
- if (!x || !y) {
- *x;
- /*[[unsafe-1]]*/
- } else {
- *x;
- /*[[safe]]*/
- }
- *x;
- /*[[unsafe-2]]*/
- }
- )";
- expectDataflow(
- OrNegatedBoth,
- UnorderedElementsAre(Pair("safe", IsSafe()), Pair("unsafe-1", IsUnsafe()),
- Pair("unsafe-2", IsUnsafe())));
-}
-
-TEST_F(PointerNullabilityTest, MemberPointers) {
- std::string DerefStructMember = R"(
- struct Foo {
- Foo* ptr;
- };
- void target(Foo foo) {
- if (foo.ptr) {
- *foo.ptr;
- /*[[safe]]*/
- } else {
- *foo.ptr;
- /*[[unsafe-1]]*/
- }
- *foo.ptr;
- /*[[unsafe-2]]*/
- }
- )";
- expectDataflow(
- DerefStructMember,
- UnorderedElementsAre(Pair("safe", IsSafe()), Pair("unsafe-1", IsUnsafe()),
- Pair("unsafe-2", IsUnsafe())));
-
- std::string DerefStructMemberInsideMemberFunction = R"(
- struct Foo {
- Foo* ptr;
- void target() {
- if (ptr) {
- *ptr;
- /*[[safe]]*/
- } else {
- *ptr;
- /*[[unsafe-1]]*/
- }
- *ptr;
- /*[[unsafe-2]]*/
- }
- };
- )";
- expectDataflow(
- DerefStructMemberInsideMemberFunction,
- UnorderedElementsAre(Pair("safe", IsSafe()), Pair("unsafe-1", IsUnsafe()),
- Pair("unsafe-2", IsUnsafe())));
-
- std::string DerefClassMember = R"(
- class Foo {
- public:
- Foo* ptr;
- };
- void target(Foo foo) {
- if (foo.ptr) {
- *foo.ptr;
- /*[[safe]]*/
- } else {
- *foo.ptr;
- /*[[unsafe-1]]*/
- }
- *foo.ptr;
- /*[[unsafe-2]]*/
- }
- )";
- expectDataflow(
- DerefClassMember,
- UnorderedElementsAre(Pair("safe", IsSafe()), Pair("unsafe-1", IsUnsafe()),
- Pair("unsafe-2", IsUnsafe())));
-}
-
-TEST_F(PointerNullabilityTest, MemberAccessOnPointer) {
- std::string MemberAccess = R"(
- struct Foo {
- void foo();
- };
- void target(Foo* foo) {
- if (foo) {
- foo->foo();
- /*[[safe]]*/
- } else {
- foo->foo();
- /*[[unsafe-1]]*/
- }
- foo->foo();
- /*[[unsafe-2]]*/
- }
- )";
- expectDataflow(
- MemberAccess,
- UnorderedElementsAre(Pair("safe", IsSafe()), Pair("unsafe-1", IsUnsafe()),
- Pair("unsafe-2", IsUnsafe())));
-
- std::string MemberAccessOnImplicitThis = R"(
- struct Foo {
- void foo();
- void target() {
- foo();
- /*[[safe]]*/
- }
- };
- )";
- expectDataflow(MemberAccessOnImplicitThis,
- UnorderedElementsAre(Pair("safe", IsSafe())));
-
- std::string MemberAccessOnExplicitThis = R"(
- struct Foo {
- void foo();
- void target() {
- this->foo();
- /*[[safe]]*/
- }
- };
- )";
- expectDataflow(MemberAccessOnExplicitThis,
- UnorderedElementsAre(Pair("safe", IsSafe())));
-
- std::string MemberAccessOnCopyOfThis = R"(
- struct Foo {
- void foo();
- void target() {
- Foo *thisCopy = this;
- thisCopy->foo();
- /*[[safe]]*/
- }
- };
- )";
- expectDataflow(MemberAccessOnCopyOfThis,
- UnorderedElementsAre(Pair("safe", IsSafe())));
-
- std::string AccessChainOnlyCheckOnFirst = R"(
- struct Foo {
- Foo* foo;
- };
- void target(Foo* foo) {
- if (foo) {
- foo->foo->foo;
- /*[[unsafe-1]]*/
- } else {
- foo->foo->foo;
- /*[[unsafe-2]]*/
- }
- foo->foo->foo;
- /*[[unsafe-3]]*/
- }
- )";
- expectDataflow(AccessChainOnlyCheckOnFirst,
- UnorderedElementsAre(Pair("unsafe-1", IsUnsafe()),
- Pair("unsafe-2", IsUnsafe()),
- Pair("unsafe-3", IsUnsafe())));
-
- std::string AccessChainCheckOnAll = R"(
- struct Foo {
- Foo* foo;
- };
- void target(Foo* foo) {
- if (foo && foo->foo) {
- foo->foo->foo;
- /*[[safe]]*/
- } else {
- foo->foo;
- /*[[unsafe-1]]*/
- }
- foo->foo;
- /*[[unsafe-2]]*/
- }
- )";
- expectDataflow(
- AccessChainCheckOnAll,
- UnorderedElementsAre(Pair("safe", IsSafe()), Pair("unsafe-1", IsUnsafe()),
- Pair("unsafe-2", IsUnsafe())));
-}
-
-} // namespace
-} // namespace nullability
-} // namespace tidy
-} // namespace clang
diff --git a/nullability_verification/pointer_nullability_diagnosis.cc b/nullability_verification/pointer_nullability_diagnosis.cc
new file mode 100644
index 0000000..6c4e4e6
--- /dev/null
+++ b/nullability_verification/pointer_nullability_diagnosis.cc
@@ -0,0 +1,62 @@
+// 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_verification/pointer_nullability_diagnosis.h"
+
+#include "nullability_verification/pointer_nullability.h"
+#include "nullability_verification/pointer_nullability_matchers.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+
+using ast_matchers::MatchFinder;
+using dataflow::Environment;
+
+namespace {
+
+llvm::Optional<const Stmt*> diagnosePointerAccess(const Stmt* PointerAccessExpr,
+ const Expr* PointerExpr,
+ const Environment& Env) {
+ auto [PointerKnown, PointerNotNull] = getPointerNullState(PointerExpr, Env);
+ auto& PointerNotKnownNull =
+ Env.makeNot(Env.makeAnd(PointerKnown, Env.makeNot(PointerNotNull)));
+ if (!Env.flowConditionImplies(PointerNotKnownNull)) {
+ return PointerAccessExpr;
+ }
+ return llvm::None;
+}
+
+llvm::Optional<const Stmt*> diagnoseDereference(const UnaryOperator* UnaryOp,
+ const MatchFinder::MatchResult&,
+ const Environment& Env) {
+ return diagnosePointerAccess(UnaryOp, UnaryOp->getSubExpr(), Env);
+}
+
+llvm::Optional<const Stmt*> diagnoseArrow(
+ const MemberExpr* MemberExpr, const MatchFinder::MatchResult& Result,
+ const Environment& Env) {
+ return diagnosePointerAccess(MemberExpr, MemberExpr->getBase(), Env);
+}
+
+auto buildDiagnoser() {
+ return dataflow::MatchSwitchBuilder<const Environment,
+ llvm::Optional<const Stmt*>>()
+ // (*)
+ .CaseOf<UnaryOperator>(isPointerDereference(), diagnoseDereference)
+ // (->)
+ .CaseOf<MemberExpr>(isPointerArrow(), diagnoseArrow)
+ .Build();
+}
+
+} // namespace
+
+PointerNullabilityDiagnoser::PointerNullabilityDiagnoser()
+ : Diagnoser(buildDiagnoser()) {}
+
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
\ No newline at end of file
diff --git a/nullability_verification/pointer_nullability_diagnosis.h b/nullability_verification/pointer_nullability_diagnosis.h
new file mode 100644
index 0000000..4d92456
--- /dev/null
+++ b/nullability_verification/pointer_nullability_diagnosis.h
@@ -0,0 +1,48 @@
+// 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_VERIFICATION_POINTER_NULLABILITY_DIAGNOSIS_H_
+#define CRUBIT_NULLABILITY_VERIFICATION_POINTER_NULLABILITY_DIAGNOSIS_H_
+
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Stmt.h"
+#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
+#include "clang/Analysis/FlowSensitive/MatchSwitch.h"
+#include "llvm/ADT/Optional.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.
+ llvm::Optional<const Stmt*> diagnose(const Stmt* Stmt, ASTContext& Ctx,
+ const dataflow::Environment& Env) {
+ return Diagnoser(*Stmt, Ctx, Env);
+ }
+
+ private:
+ dataflow::MatchSwitch<const dataflow::Environment,
+ llvm::Optional<const Stmt*>>
+ Diagnoser;
+};
+
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
+
+#endif // CRUBIT_NULLABILITY_VERIFICATION_POINTER_NULLABILITY_DIAGNOSIS_H_
diff --git a/nullability_verification/pointer_nullability_matchers.cc b/nullability_verification/pointer_nullability_matchers.cc
index e1c95d5..300547a 100644
--- a/nullability_verification/pointer_nullability_matchers.cc
+++ b/nullability_verification/pointer_nullability_matchers.cc
@@ -48,9 +48,10 @@
Matcher<Stmt> isImplicitCastPointerToBool() {
return implicitCastExpr(hasCastKind(CK_PointerToBoolean));
}
-Matcher<Stmt> isMemberExprInvolvingPointers() {
- return memberExpr(anyOf(isArrow(), hasType(isAnyPointer())));
+Matcher<Stmt> isMemberOfPointerType() {
+ return memberExpr(hasType(isAnyPointer()));
}
+Matcher<Stmt> isPointerArrow() { return memberExpr(isArrow()); }
Matcher<Stmt> isCXXThisExpr() { return cxxThisExpr(); }
} // namespace nullability
} // namespace tidy
diff --git a/nullability_verification/pointer_nullability_matchers.h b/nullability_verification/pointer_nullability_matchers.h
index 23b42b8..c5562ae 100644
--- a/nullability_verification/pointer_nullability_matchers.h
+++ b/nullability_verification/pointer_nullability_matchers.h
@@ -12,7 +12,8 @@
namespace nullability {
ast_matchers::internal::Matcher<Stmt> isPointerVariableReference();
-ast_matchers::internal::Matcher<Stmt> isMemberExprInvolvingPointers();
+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();
diff --git a/nullability_verification/pointer_nullability_verification_test.cc b/nullability_verification/pointer_nullability_verification_test.cc
new file mode 100644
index 0000000..0ae5cc4
--- /dev/null
+++ b/nullability_verification/pointer_nullability_verification_test.cc
@@ -0,0 +1,458 @@
+// 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 <string>
+
+#include "nullability_verification/pointer_nullability_analysis.h"
+#include "nullability_verification/pointer_nullability_diagnosis.h"
+#include "clang/Basic/SourceManager.h"
+#include "third_party/llvm/llvm-project/clang/unittests/Analysis/FlowSensitive/TestingSupport.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Testing/Support/Error.h"
+#include "third_party/llvm/llvm-project/llvm/utils/unittest/googletest/include/gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+namespace {
+
+using dataflow::Environment;
+using dataflow::TypeErasedDataflowAnalysisState;
+using dataflow::test::AnalysisData;
+using dataflow::test::checkDataflow;
+using ::testing::ContainerEq;
+using ::testing::Test;
+
+void checkDiagnostics(llvm::StringRef SourceCode) {
+ std::vector<const Stmt *> Diagnostics;
+ PointerNullabilityDiagnoser Diagnoser;
+ ASSERT_THAT_ERROR(
+ checkDataflow<PointerNullabilityAnalysis>(
+ SourceCode, ast_matchers::hasName("target"),
+ [](ASTContext &ASTCtx, Environment &) {
+ return PointerNullabilityAnalysis(ASTCtx);
+ },
+ [&Diagnostics, &Diagnoser](
+ ASTContext &Ctx, const Stmt *Stmt,
+ const TypeErasedDataflowAnalysisState &State) {
+ auto StmtDiagnostics = Diagnoser.diagnose(Stmt, Ctx, State.Env);
+ if (StmtDiagnostics.has_value()) {
+ Diagnostics.push_back(StmtDiagnostics.value());
+ }
+ },
+ [&Diagnostics](AnalysisData AnalysisData) {
+ llvm::DenseSet<unsigned> ExpectedLines, ActualLines;
+ auto &SrcMgr = AnalysisData.ASTCtx.getSourceManager();
+ for (auto [Stmt, _] : AnalysisData.Annotations) {
+ ExpectedLines.insert(
+ SrcMgr.getPresumedLineNumber(Stmt->getBeginLoc()));
+ }
+ for (auto *Stmt : Diagnostics) {
+ ActualLines.insert(
+ SrcMgr.getPresumedLineNumber(Stmt->getBeginLoc()));
+ }
+ EXPECT_THAT(ActualLines, ContainerEq(ExpectedLines));
+ },
+ {"-fsyntax-only", "-std=c++17", "-Wno-unused-value"}),
+ llvm::Succeeded());
+}
+
+TEST(PointerNullabilityTest, NoPointerOperations) {
+ checkDiagnostics(R"(
+ void target() {
+ 1 + 2;
+ }
+ )");
+}
+
+TEST(PointerNullabilityTest, DereferenceWithoutACheck) {
+ checkDiagnostics(R"(
+ void target(int* maybeNull) {
+ *maybeNull; // [[unsafe]]
+ }
+ )");
+}
+
+TEST(PointerNullabilityTest, InitializedWithNullPtrLiteral) {
+ checkDiagnostics(R"(
+ void target() {
+ int *null = nullptr;
+ *null; // [[unsafe]]
+ }
+ )");
+
+ checkDiagnostics(R"(
+ void target() {
+ int *null = 0;
+ *null; // [[unsafe]]
+ }
+ )");
+}
+
+TEST(PointerNullabilityTest, InitializedWithAddressOf) {
+ checkDiagnostics(R"(
+ void target(int x) {
+ int *nonNull = &x;
+ *nonNull;
+ }
+ )");
+}
+
+TEST(PointerNullabilityTest, InitializedWithOtherPointer) {
+ checkDiagnostics(R"(
+ void target(int x) {
+ int *nonNull = &x;
+ int *nonNullCopy = nonNull;
+ *nonNullCopy;
+ }
+ )");
+
+ checkDiagnostics(R"(
+ void target(int* nullable) {
+ int *nullableCopy = nullable;
+ *nullableCopy; // [[unsafe]]
+ }
+ )");
+
+ checkDiagnostics(R"(
+ void target(int* nullable) {
+ int *nullableCopy = nullable;
+ if (nullable) {
+ *nullableCopy;
+ } else {
+ *nullableCopy; // [[unsafe]]
+ }
+ *nullableCopy; // [[unsafe]]
+ }
+ )");
+
+ checkDiagnostics(R"(
+ void target(int* nullable) {
+ int *nullableCopy = nullable;
+ if (nullableCopy) {
+ *nullable;
+ } else {
+ *nullable; // [[unsafe]]
+ }
+ *nullable; // [[unsafe]]
+ }
+ )");
+}
+
+TEST(PointerNullabilityTest, CheckByComparisonToNullPtr) {
+ checkDiagnostics(R"(
+ void target(int *maybeNull) {
+ if (maybeNull != nullptr) {
+ *maybeNull;
+ } else {
+ *maybeNull; // [[unsafe]]
+ }
+ *maybeNull; // [[unsafe]]
+ }
+ )");
+
+ checkDiagnostics(R"(
+ void target(int *maybeNull) {
+ if (nullptr != maybeNull) {
+ *maybeNull;
+ } else {
+ *maybeNull; // [[unsafe]]
+ }
+ *maybeNull; // [[unsafe]]
+ }
+ )");
+
+ checkDiagnostics(R"(
+ void target(int* maybeNull) {
+ if (maybeNull == nullptr) {
+ *maybeNull; // [[unsafe]]
+ } else {
+ *maybeNull;
+ }
+ *maybeNull; // [[unsafe]]
+ }
+ )");
+
+ checkDiagnostics(R"(
+ void target(int* maybeNull) {
+ if (nullptr == maybeNull) {
+ *maybeNull; // [[unsafe]]
+ } else {
+ *maybeNull;
+ }
+ *maybeNull; // [[unsafe]]
+ }
+ )");
+}
+
+TEST(PointerNullabilityTest, CheckByImplicitCastToBool) {
+ checkDiagnostics(R"(
+ void target(int* maybeNull) {
+ if (maybeNull) {
+ *maybeNull;
+ } else {
+ *maybeNull; // [[unsafe]]
+ }
+ *maybeNull; // [[unsafe]]
+ }
+ )");
+
+ checkDiagnostics(R"(
+ void target(int* maybeNull) {
+ if (!maybeNull) {
+ *maybeNull; // [[unsafe]]
+ } else {
+ *maybeNull;
+ }
+ *maybeNull; // [[unsafe]]
+ }
+ )");
+}
+
+TEST(PointerNullabilityTest, CheckByComparisonToOtherNullPtr) {
+ checkDiagnostics(R"(
+ void target(int* maybeNull) {
+ int *null = nullptr;
+ if (maybeNull != null) {
+ *maybeNull;
+ } else {
+ *maybeNull; // [[unsafe]]
+ }
+ *maybeNull; // [[unsafe]]
+ }
+ )");
+
+ checkDiagnostics(R"(
+ void target(int* maybeNull) {
+ int *null = nullptr;
+ if (maybeNull == null) {
+ *maybeNull; // [[unsafe]]
+ } else {
+ *maybeNull;
+ }
+ *maybeNull; // [[unsafe]]
+ }
+ )");
+}
+
+TEST(PointerNullabilityTest, CheckByComparisonToOtherNonNullPtr) {
+ checkDiagnostics(R"(
+ void target(int* maybeNull, int x) {
+ int* nonNull = &x;
+ if (maybeNull != nonNull) {
+ *maybeNull; // [[unsafe]]
+ } else {
+ *maybeNull;
+ }
+ *maybeNull; // [[unsafe]]
+ }
+ )");
+
+ checkDiagnostics(R"(
+ void target(int* maybeNull, int x) {
+ int* nonNull = &x;
+ if (maybeNull == nonNull) {
+ *maybeNull;
+ } else {
+ *maybeNull; // [[unsafe]]
+ }
+ *maybeNull; // [[unsafe]]
+ }
+ )");
+}
+
+TEST(PointerNullabilityTest, CheckByComparisonToOtherUnknownPtr) {
+ checkDiagnostics(R"(
+ void target(int* x, int* y) {
+ if (x != y) {
+ *x; // [[unsafe]]
+ } else {
+ *x; // [[unsafe]]
+ }
+ *x; // [[unsafe]]
+ }
+ )");
+
+ checkDiagnostics(R"(
+ void target(int* x, int* y) {
+ if (x == y) {
+ *x; // [[unsafe]]
+ } else {
+ *x; // [[unsafe]]
+ }
+ *x; // [[unsafe]]
+ }
+ )");
+}
+
+TEST(PointerNullabilityTest, BinaryExpressions) {
+ checkDiagnostics(R"(
+ void target(int* x, int* y) {
+ if (x && y) {
+ *x;
+ } else {
+ *x; // [[unsafe]]
+ }
+ *x; // [[unsafe]]
+ }
+ )");
+
+ checkDiagnostics(R"(
+ void target(int* x, int* y) {
+ if (x || y) {
+ *x; // [[unsafe]]
+ } else {
+ *x; // [[unsafe]]
+ }
+ *x; // [[unsafe]]
+ }
+ )");
+
+ checkDiagnostics(R"(
+ void target(int* x, int* y) {
+ if (!x && !y) {
+ *x; // [[unsafe]]
+ } else {
+ *x; // [[unsafe]]
+ }
+ *x; // [[unsafe]]
+ }
+ )");
+
+ checkDiagnostics(R"(
+ void target(int* x, int* y) {
+ if (!x || !y) {
+ *x; // [[unsafe]]
+ } else {
+ *x;
+ }
+ *x; // [[unsafe]]
+ }
+ )");
+}
+
+TEST(PointerNullabilityTest, MemberPointers) {
+ checkDiagnostics(R"(
+ struct Foo {
+ Foo* ptr;
+ };
+ void target(Foo foo) {
+ if (foo.ptr) {
+ *foo.ptr;
+ } else {
+ *foo.ptr; // [[unsafe]]
+ }
+ *foo.ptr; // [[unsafe]]
+ }
+ )");
+
+ checkDiagnostics(R"(
+ struct Foo {
+ Foo* ptr;
+ void target() {
+ if (ptr) {
+ *ptr;
+ } else {
+ *ptr; // [[unsafe]]
+ }
+ *ptr; // [[unsafe]]
+ }
+ };
+ )");
+
+ checkDiagnostics(R"(
+ class Foo {
+ public:
+ Foo* ptr;
+ };
+ void target(Foo foo) {
+ if (foo.ptr) {
+ *foo.ptr;
+ } else {
+ *foo.ptr; // [[unsafe]]
+ }
+ *foo.ptr; // [[unsafe]]
+ }
+ )");
+}
+
+TEST(PointerNullabilityTest, MemberAccessOnPointer) {
+ checkDiagnostics(R"(
+ struct Foo {
+ void foo();
+ };
+ void target(Foo* foo) {
+ if (foo) {
+ foo->foo();
+ } else {
+ foo->foo(); // [[unsafe]]
+ }
+ foo->foo(); // [[unsafe]]
+ }
+ )");
+
+ checkDiagnostics(R"(
+ struct Foo {
+ void foo();
+ void target() {
+ foo();
+ }
+ };
+ )");
+
+ checkDiagnostics(R"(
+ struct Foo {
+ void foo();
+ void target() {
+ this->foo();
+ }
+ };
+ )");
+
+ checkDiagnostics(R"(
+ struct Foo {
+ void foo();
+ void target() {
+ Foo *thisCopy = this;
+ thisCopy->foo();
+ }
+ };
+ )");
+
+ checkDiagnostics(R"(
+ struct Foo {
+ Foo* foo;
+ };
+ void target(Foo* foo) {
+ if (foo) {
+ foo->foo->foo; // [[unsafe]]
+ } else {
+ foo->foo->foo; // [[unsafe]]
+ }
+ foo->foo->foo; // [[unsafe]]
+ }
+ )");
+
+ checkDiagnostics(R"(
+ struct Foo {
+ Foo* foo;
+ };
+ void target(Foo* foo) {
+ if (foo && foo->foo) {
+ foo->foo->foo;
+ } else {
+ foo->foo; // [[unsafe]]
+ }
+ foo->foo; // [[unsafe]]
+ }
+ )");
+}
+
+} // namespace
+} // namespace nullability
+} // namespace tidy
+} // namespace clang