[clang] [clang-tools-extra] [clang][dataflow] Add null-check after dereference checker (PR #84166)

via cfe-commits cfe-commits at lists.llvm.org
Wed Mar 6 05:22:36 PST 2024


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-analysis

@llvm/pr-subscribers-clangd

Author: Discookie (Discookie)

<details>
<summary>Changes</summary>

This checker implements a null-pointer analysis checker using the data-flow framework. In particular, the checker finds cases where the pointer's nullability is checked after it has been dereferenced:

```
int f(int *ptr) {
  *ptr += 20; // note: one of the locations where the pointer's value cannot be null
  
  if (ptr) {
    *ptr += 42; // warning: pointer value is checked even though it cannot be null at this point
    return *ptr;
  }
  return 0;
```

It currently recognizes the following operations:
- Dereference and arrow operators
- Pointer-to-boolean cast
- Assigning &obj, nullptr and other values

---

Notable limitations:

The checker only supports C++ due to the limitations of the framework (llvm/llvm-project#<!-- -->65301).

Function calls taking a reference of a pointer are not handled yet.
``void external(int *&ref);``

The note tag is only displayed at one location, and not all operations are supported or displayed.

More examples and limitations in the [checker docs](https://github.com/Discookie/llvm-project/blob/reverse-null/clang-tools-extra/docs/clang-tidy/checks/bugprone/null-check-after-dereference.rst).

---

Results on open-source projects:
https://codechecker-demo.eastus.cloudapp.azure.com/Default/runs?run=Discookie_reverse-null

The reference-of-ptr limitation leads to a class of false positives across the tested projects [(example)](https://codechecker-demo.eastus.cloudapp.azure.com/Default/report-detail?run=libwebm_libwebm-1.0.0.27_Discookie_reverse-null&detection-status=New&report-id=3497414&report-hash=e75b81fcd825616e0ec904ffef1c99ac&report-filepath=%2Flocal%2Fpersistent_docker%2FCSA-measurements-driver-2923%2Fmeasurements_workspace%2Flibwebm%2Fmkvparser.cpp).

But the checker also found quite a few true positives, on LLVM: [1](https://codechecker-demo.eastus.cloudapp.azure.com/Default/report-detail?run=llvm-project_llvmorg-12.0.0_Discookie_reverse-null&report-id=3503712&report-hash=63f82f7e62ecfea1c53c1896c29b2ef2&report-filepath=%2Flocal%2Fpersistent_docker%2FCSA-measurements-driver-2923%2Fmeasurements_workspace%2Fllvm-project%2Fclang%2Finclude%2Fclang%2FBasic%2FDiagnostic.h&is-unique=off&diff-type=New&review-status=Confirmed%20bug) [2](https://codechecker-demo.eastus.cloudapp.azure.com/Default/report-detail?run=%2aDiscookie%2a&detection-status=New&report-id=3503771&report-hash=1f5c708ee2cfe736068e8f8a3fdc8634&report-filepath=%2Flocal%2Fpersistent_docker%2FCSA-measurements-driver-2923%2Fmeasurements_workspace%2Fllvm-project%2Fclang%2Flib%2FAnalysis%2FCalledOnceCheck.cpp&page=3&is-unique=off&diff-type=New&review-status=Unreviewed) [3](https://codechecker-demo.eastus.cloudapp.azure.com/Default/report-detail?run=%2aDiscookie%2a&detection-status=New&report-id=3503770&report-hash=53c9082086ce7d320cf5e5aab78dbab6&report-filepath=%2Flocal%2Fpersistent_docker%2FCSA-measurements-driver-2923%2Fmeasurements_workspace%2Fllvm-project%2Fllvm%2Flib%2FMC%2FMCSymbol.cpp&page=3&is-unique=off&diff-type=New&review-status=Unreviewed) and a couple more, listed [here](https://github.com/Discookie/llvm-project/pull/1#issuecomment-1979214081).


---

This checker corresponds to the the CERT rule [EXP34-C, Do not dereference null pointers](https://wiki.sei.cmu.edu/confluence/display/c/EXP34-C.+Do+not+dereference+null+pointers#EXP34C.Donotdereferencenullpointers-NoncompliantCodeExample.2), example 3.


---

Patch is 63.42 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/84166.diff


12 Files Affected:

- (modified) clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp (+3) 
- (modified) clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt (+1) 
- (added) clang-tools-extra/clang-tidy/bugprone/NullCheckAfterDereferenceCheck.cpp (+175) 
- (added) clang-tools-extra/clang-tidy/bugprone/NullCheckAfterDereferenceCheck.h (+37) 
- (modified) clang-tools-extra/clangd/TidyProvider.cpp (+2-1) 
- (added) clang-tools-extra/docs/clang-tidy/checks/bugprone/null-check-after-dereference.rst (+158) 
- (added) clang-tools-extra/test/clang-tidy/checkers/bugprone/null-check-after-dereference.cpp (+330) 
- (added) clang/include/clang/Analysis/FlowSensitive/Models/NullPointerAnalysisModel.h (+112) 
- (modified) clang/lib/Analysis/FlowSensitive/Models/CMakeLists.txt (+1) 
- (added) clang/lib/Analysis/FlowSensitive/Models/NullPointerAnalysisModel.cpp (+629) 
- (modified) clang/unittests/Analysis/FlowSensitive/CMakeLists.txt (+1) 
- (added) clang/unittests/Analysis/FlowSensitive/NullPointerAnalysisModelTest.cpp (+336) 


``````````diff
diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
index a8a23b045f80bb..ddd708dd513355 100644
--- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
@@ -48,6 +48,7 @@
 #include "NoEscapeCheck.h"
 #include "NonZeroEnumToBoolConversionCheck.h"
 #include "NotNullTerminatedResultCheck.h"
+#include "NullCheckAfterDereferenceCheck.h"
 #include "OptionalValueConversionCheck.h"
 #include "ParentVirtualCallCheck.h"
 #include "PosixReturnCheck.h"
@@ -180,6 +181,8 @@ class BugproneModule : public ClangTidyModule {
     CheckFactories.registerCheck<PosixReturnCheck>("bugprone-posix-return");
     CheckFactories.registerCheck<ReservedIdentifierCheck>(
         "bugprone-reserved-identifier");
+    CheckFactories.registerCheck<NullCheckAfterDereferenceCheck>(
+        "bugprone-null-check-after-dereference");
     CheckFactories.registerCheck<SharedPtrArrayMismatchCheck>(
         "bugprone-shared-ptr-array-mismatch");
     CheckFactories.registerCheck<SignalHandlerCheck>("bugprone-signal-handler");
diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
index 1cd6fb207d7625..5dbe761cb810e7 100644
--- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
@@ -44,6 +44,7 @@ add_clang_library(clangTidyBugproneModule
   NoEscapeCheck.cpp
   NonZeroEnumToBoolConversionCheck.cpp
   NotNullTerminatedResultCheck.cpp
+  NullCheckAfterDereferenceCheck.cpp
   OptionalValueConversionCheck.cpp
   ParentVirtualCallCheck.cpp
   PosixReturnCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/NullCheckAfterDereferenceCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/NullCheckAfterDereferenceCheck.cpp
new file mode 100644
index 00000000000000..1ba4edc1eaf0e4
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/NullCheckAfterDereferenceCheck.cpp
@@ -0,0 +1,175 @@
+//===--- NullCheckAfterDereferenceCheck.cpp - clang-tidy-------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "NullCheckAfterDereferenceCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Analysis/CFG.h"
+#include "clang/Analysis/FlowSensitive/ControlFlowContext.h"
+#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
+#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
+#include "clang/Analysis/FlowSensitive/DataflowLattice.h"
+#include "clang/Analysis/FlowSensitive/Models/NullPointerAnalysisModel.h"
+#include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h"
+#include "clang/Basic/SourceLocation.h"
+#include "llvm/ADT/Any.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/Support/Error.h"
+#include <memory>
+#include <vector>
+
+namespace clang::tidy::bugprone {
+
+using ast_matchers::MatchFinder;
+using dataflow::NullPointerAnalysisModel;
+using dataflow::NullCheckAfterDereferenceDiagnoser;
+
+static constexpr llvm::StringLiteral FuncID("fun");
+
+struct ExpandedResult {
+  SourceLocation WarningLoc;
+  std::optional<SourceLocation> DerefLoc;
+};
+
+using ExpandedResultType = std::pair<std::vector<ExpandedResult>,
+                                     std::vector<ExpandedResult>>;
+
+static std::optional<ExpandedResultType> analyzeFunction(
+        const FunctionDecl &FuncDecl) {
+  using dataflow::ControlFlowContext;
+  using dataflow::DataflowAnalysisState;
+  using llvm::Expected;
+
+  ASTContext &ASTCtx = FuncDecl.getASTContext();
+
+  if (FuncDecl.getBody() == nullptr) {
+    return std::nullopt;
+  }
+
+  Expected<ControlFlowContext> Context =
+      ControlFlowContext::build(FuncDecl, *FuncDecl.getBody(), ASTCtx);
+  if (!Context)
+    return std::nullopt;
+
+  dataflow::DataflowAnalysisContext AnalysisContext(
+      std::make_unique<dataflow::WatchedLiteralsSolver>());
+  dataflow::Environment Env(AnalysisContext, FuncDecl);
+  NullPointerAnalysisModel Analysis(ASTCtx);
+  NullCheckAfterDereferenceDiagnoser Diagnoser;
+  NullCheckAfterDereferenceDiagnoser::ResultType Diagnostics;
+
+  using LatticeState = DataflowAnalysisState<NullPointerAnalysisModel::Lattice>;
+  using DetailMaybeLatticeStates = std::vector<std::optional<LatticeState>>;
+
+  auto DiagnoserImpl = [&ASTCtx, &Diagnoser, &Diagnostics](
+              const CFGElement &Elt,
+              const LatticeState &S) mutable -> void {
+            auto EltDiagnostics = Diagnoser.diagnose(ASTCtx, &Elt, S.Env);
+            llvm::move(EltDiagnostics.first,
+                       std::back_inserter(Diagnostics.first));
+            llvm::move(EltDiagnostics.second,
+                       std::back_inserter(Diagnostics.second));
+          };
+
+  Expected<DetailMaybeLatticeStates>
+      BlockToOutputState = dataflow::runDataflowAnalysis(
+          *Context, Analysis, Env, DiagnoserImpl);
+
+  if (llvm::Error E = BlockToOutputState.takeError()) {
+    llvm::dbgs() << "Dataflow analysis failed: " << llvm::toString(std::move(E))
+                 << ".\n";
+    return std::nullopt;
+  }
+
+  ExpandedResultType ExpandedDiagnostics;
+
+  llvm::transform(Diagnostics.first,
+                  std::back_inserter(ExpandedDiagnostics.first),
+                  [&](SourceLocation WarningLoc) -> ExpandedResult {
+                    if (auto Val = Diagnoser.WarningLocToVal[WarningLoc];
+                        auto DerefExpr = Diagnoser.ValToDerefLoc[Val]) {
+                      return {WarningLoc, DerefExpr->getBeginLoc()};
+                    }
+
+                    return {WarningLoc, std::nullopt};
+                  });
+
+  llvm::transform(Diagnostics.second,
+                  std::back_inserter(ExpandedDiagnostics.second),
+                  [&](SourceLocation WarningLoc) -> ExpandedResult {
+                    if (auto Val = Diagnoser.WarningLocToVal[WarningLoc];
+                        auto DerefExpr = Diagnoser.ValToDerefLoc[Val]) {
+                      return {WarningLoc, DerefExpr->getBeginLoc()};
+                    }
+
+                    return {WarningLoc, std::nullopt};
+                  });
+
+  return ExpandedDiagnostics;
+}
+
+void NullCheckAfterDereferenceCheck::registerMatchers(MatchFinder *Finder) {
+  using namespace ast_matchers;
+
+  auto hasPointerValue =
+      hasDescendant(NullPointerAnalysisModel::ptrValueMatcher());
+  Finder->addMatcher(
+      decl(anyOf(functionDecl(unless(isExpansionInSystemHeader()),
+                              // FIXME: Remove the filter below when lambdas are
+                              // well supported by the check.
+                              unless(hasDeclContext(cxxRecordDecl(isLambda()))),
+                              hasBody(hasPointerValue)),
+                 cxxConstructorDecl(hasAnyConstructorInitializer(
+                     withInitializer(hasPointerValue)))))
+          .bind(FuncID),
+      this);
+}
+
+void NullCheckAfterDereferenceCheck::check(
+    const MatchFinder::MatchResult &Result) {
+  if (Result.SourceManager->getDiagnostics().hasUncompilableErrorOccurred())
+    return;
+
+  const auto *FuncDecl = Result.Nodes.getNodeAs<FunctionDecl>(FuncID);
+  assert(FuncDecl && "invalid FuncDecl matcher"); 
+  if (FuncDecl->isTemplated())
+    return;
+
+  if (const auto Diagnostics = analyzeFunction(*FuncDecl)) {
+    const auto& [CheckWhenNullLocations, CheckAfterDereferenceLocations] =
+        *Diagnostics;
+
+    for (const auto [WarningLoc, DerefLoc] : CheckAfterDereferenceLocations) {
+      diag(WarningLoc,
+           "pointer value is checked even though "
+           "it cannot be null at this point");
+
+      if (DerefLoc) {
+        diag(*DerefLoc,
+             "one of the locations where the pointer's value cannot be null",
+             DiagnosticIDs::Note);
+      }
+    }
+
+    for (const auto [WarningLoc, DerefLoc] : CheckWhenNullLocations) {
+      diag(WarningLoc,
+           "pointer value is checked but it can only be null at this point");
+      
+      if (DerefLoc) {
+        diag(*DerefLoc,
+             "one of the locations where the pointer's value can only be null",
+             DiagnosticIDs::Note);
+      }
+    }
+  }
+}
+
+} // namespace clang::tidy::bugprone
diff --git a/clang-tools-extra/clang-tidy/bugprone/NullCheckAfterDereferenceCheck.h b/clang-tools-extra/clang-tidy/bugprone/NullCheckAfterDereferenceCheck.h
new file mode 100644
index 00000000000000..e5ac27e79deb11
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/NullCheckAfterDereferenceCheck.h
@@ -0,0 +1,37 @@
+//===--- NullCheckAfterDereferenceCheck.h - clang-tidy ----------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_NULLCHECKAFTERDEREFERENCECHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_NULLCHECKAFTERDEREFERENCECHECK_H
+
+#include "../ClangTidyCheck.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+namespace clang::tidy::bugprone {
+
+/// Finds checks for pointer nullability after a pointer has already been
+/// dereferenced, using the data-flow framework.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/null-check-after-dereference.html
+class NullCheckAfterDereferenceCheck : public ClangTidyCheck {
+public:
+  NullCheckAfterDereferenceCheck(StringRef Name, ClangTidyContext *Context)
+      : ClangTidyCheck(Name, Context) {}
+
+  // The data-flow framework does not support C because of AST differences.
+  bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+    return LangOpts.CPlusPlus;
+  }
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+};
+
+} // namespace clang::tidy::bugprone
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_NULLCHECKAFTERDEREFERENCECHECK_H
diff --git a/clang-tools-extra/clangd/TidyProvider.cpp b/clang-tools-extra/clangd/TidyProvider.cpp
index b658a80559937c..cf7b4dff16070b 100644
--- a/clang-tools-extra/clangd/TidyProvider.cpp
+++ b/clang-tools-extra/clangd/TidyProvider.cpp
@@ -219,9 +219,10 @@ TidyProvider disableUnusableChecks(llvm::ArrayRef<std::string> ExtraBadChecks) {
       "-bugprone-use-after-move",
       // Alias for bugprone-use-after-move.
       "-hicpp-invalid-access-moved",
-      // Check uses dataflow analysis, which might hang/crash unexpectedly on
+      // Checks use dataflow analysis, which might hang/crash unexpectedly on
       // incomplete code.
       "-bugprone-unchecked-optional-access",
+      "-bugprone-null-check-after-dereference",
 
       // ----- Performance problems -----
 
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/null-check-after-dereference.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/null-check-after-dereference.rst
new file mode 100644
index 00000000000000..90e8897d7817ed
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/null-check-after-dereference.rst
@@ -0,0 +1,158 @@
+.. title:: clang-tidy - bugprone-null-check-after-dereference
+
+bugprone-null-check-after-dereference
+=====================================
+
+.. note::
+
+   This check uses a flow-sensitive static analysis to produce its
+   results. Therefore, it may be more resource intensive (RAM, CPU) than the
+   average clang-tidy check.
+
+This check identifies redundant pointer null-checks, by finding cases where the
+pointer cannot be null at the location of the null-check.
+
+Redundant null-checks can signal faulty assumptions about the current value of
+a pointer at different points in the program. Either the null-check is
+redundant, or there could be a null-pointer dereference earlier in the program.
+
+.. code-block:: c++
+
+   int f(int *ptr) {
+     *ptr = 20; // note: one of the locations where the pointer's value cannot be null
+     // ...
+     if (ptr) { // bugprone: pointer is checked even though it cannot be null at this point
+       return *ptr;
+     }
+     return 0;
+   }
+
+Supported pointer operations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Pointer null-checks
+-------------------
+
+The checker currently supports null-checks on pointers that use
+``operator bool``, such as when being used as the condition
+for an `if` statement.
+
+.. code-block:: c++
+
+   int f(int *ptr) {
+     if (ptr) {
+       if (ptr) { // bugprone: pointer is re-checked after its null-ness is already checked.
+         return *ptr;
+       }
+
+       return ptr ? *ptr : 0; // bugprone: pointer is re-checked after its null-ness is already checked.
+     }
+     return 0;
+   }
+
+Pointer dereferences
+--------------------
+
+Pointer star- and arrow-dereferences are supported.
+
+.. code-block:: c++
+
+   struct S {
+     int val;
+   };
+
+   void f(int *ptr, S *wrapper) {
+     *ptr = 20;
+     wrapper->val = 15;
+   }
+
+Null-pointer and other value assignments
+----------------------------------------
+
+The checker supports assigning various values to pointers, making them *null*
+or *non-null*. The checker also supports passing pointers of a pointer to
+external functions.
+
+.. code-block:: c++
+
+   extern int *external();
+   extern void refresh(int **ptr_ptr);
+   
+   int f() {
+     int *ptr_null = nullptr;
+     if (ptr_null) { // bugprone: pointer is checked where it cannot be non-null.
+       return *ptr_null;
+     }
+
+     int *ptr = external();
+     if (ptr) { // safe: external() could return either nullable or nonnull pointers.
+       return *ptr;
+     }
+
+     int *ptr2 = external();
+     *ptr2 = 20;
+     refresh(&ptr2);
+     if (ptr2) { // safe: pointer could be changed by refresh().
+       return *ptr2;
+     }
+     return 0;
+   }
+
+Limitations
+~~~~~~~~~~~
+
+The check only supports C++ due to limitations in the data-flow framework.
+
+The check currently does not output the locations of the previous
+dereferences, even in simple cases.
+
+The annotations ``_nullable`` and ``_nonnull`` are not supported.
+
+.. code-block:: c++
+
+   extern int *_nonnull external_nonnull();
+
+   int annotations() {
+     int *ptr = external_nonnull();
+
+     return ptr ? *ptr : 0; // false-negative: pointer is known to be non-null.
+   }
+
+Function calls taking a pointer value as a reference or a pointer-to-pointer are not supported.
+
+.. code-block:: c++
+   extern int *external();
+   extern void refresh_ref(int *&ptr);
+   extern void refresh_ptr(int **ptr);
+
+   int extern_ref() {
+     int *ptr = external();
+     *ptr = 20;
+
+     refresh_ref(ptr);
+     refresh_ptr(&ptr);
+
+     return ptr ? *ptr : 0; // false-positive: pointer could be changed by refresh_ref().
+   }
+
+Note tags are currently appended to a single location, even if all paths ensure a pointer is not null.
+
+.. code-block:: c++
+   int branches(int *p, bool b) {
+     if (b) {
+       *p = 42; // true-positive: note-tag appended here
+     } else {
+       *p = 20; // false-positive: note tag not appended here
+     }
+
+     return ptr ? *ptr : 0;
+   }
+
+Declarations and some other operations are not supported by note tags yet. This can sometimes result in erroneous note tags being shown instead of the correct one.
+
+.. code-block:: c++
+   int note_tags() {
+      int *ptr = nullptr; // false-negative: note tag not shown
+
+      return ptr ? *ptr : 0;
+   }
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/null-check-after-dereference.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/null-check-after-dereference.cpp
new file mode 100644
index 00000000000000..21e9eff4290f7a
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/null-check-after-dereference.cpp
@@ -0,0 +1,330 @@
+// RUN: %check_clang_tidy %s bugprone-null-check-after-dereference %t
+
+struct S {
+  int a;
+};
+
+int warning_deref(int *p) {
+  *p = 42;
+
+  if (p) {
+    // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: pointer value is checked even though it cannot be null at this point [bugprone-null-check-after-dereference]
+    // CHECK-MESSAGES: :[[@LINE-4]]:3: note: one of the locations where the pointer's value cannot be null
+  // FIXME: If there's a direct path, make the error message more precise, ie. remove `one of the locations`
+    *p += 20;
+    return *p;
+  } else {
+    return 0;
+  }
+}
+
+int warning_member(S *q) {
+  q->a = 42;
+
+  if (q) {
+    // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: pointer value is checked even though it cannot be null at this point
+    // CHECK-MESSAGES: :[[@LINE-4]]:3: note: one of the locations where the pointer's value cannot be null
+    q->a += 20;
+    return q->a;
+  } else {
+    return 0;
+  }
+}
+
+int negative_warning(int *p) {
+  *p = 42;
+
+  if (!p) {
+    // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: pointer value is checked even though it cannot be null at this point
+    // CHECK-MESSAGES: :[[@LINE-4]]:3: note: one of the locations where the pointer's value cannot be null
+    return 0;
+  } else {
+    *p += 20;
+    return *p;
+  }
+}
+
+int no_warning(int *p, bool b) {
+  if (b) {
+    *p = 42;
+  }
+
+  if (p) {
+    // CHECK-MESSAGES-NOT: :[[@LINE-1]]:7: warning: pointer value is checked even though it cannot be null at this point 
+    *p += 20;
+    return *p;
+  } else {
+    return 0;
+  }
+}
+
+int else_branch_warning(int *p, bool b) {
+  if (b) {
+    *p = 42;
+  } else {
+    *p = 20;
+  }
+
+  if (p) {
+    // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: pointer value is checked even though it cannot be null at this point
+    // CHECK-MESSAGES: :[[@LINE-7]]:5: note: one of the locations where the pointer's value cannot be null
+    return 0;
+  } else {
+    *p += 20;
+    return *p;
+  }
+}
+
+int two_branches_warning(int *p, bool b) {
+  if (b) {
+    *p = 42;
+  }
+  
+  if (!b) {
+    *p = 20;
+  }
+
+  if (p) {
+    // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: pointer value is checked even though it cannot be null at this point
+    // CHECK-MESSAGES: :[[@LINE-9]]:5: note: one of the locations where the pointer's value cannot be null
+    return 0;
+  } else {
+    *p += 20;
+    return *p;
+  }
+}
+
+int two_branches_reversed(int *p, bool b) {
+  if (!b) {
+    *p = 42;
+  }
+  
+  if (b) {
+    *p = 20;
+  }
+
+  if (p) {
+    // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: pointer value is checked even though it cannot be null at this point
+    // CHECK-MESSAGES: :[[@LINE-9]]:5: note: one of the locations where the pointer's value cannot be null
+    return 0;
+  } else {
+    *p += 20;
+    return *p;
+  }
+}
+
+
+int regular_assignment(int *p, int *q) {
+  *p = 42;
+  q = p;
+
+  if (q) {
+    // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: pointer value is checked even though it cannot be null at this point
+    // CHECK-MESSAGES: :[[@LINE-5]]:3: note: one of the locations where the pointer's value cannot be null
+    *p += 20; 
+    return *p;
+  } else {
+    return 0;
+  }
+}
+
+int nullptr_assignment(int *nullptr_param, bool b) {
+  *nullptr_param = 42;
+  int *nullptr_assigned;
+
+  if (b) {
+    nullptr_assigned = nullptr;
+  } else {
+    nullptr_assigned = nullptr_param;
+  }
+
+  if (nullptr_assigned) {
+    // CHECK-MESSAGES-NOT: :[[@LINE-1]]:7: warning: pointer value is checked even though it cannot be null at this point
+    *nullptr_assigned = 20;
+    return *nullptr_assigned;
+  } else {
+    return 0;
+  }
+}
+
+extern int *fncall();
+extern void refresh_ref(int *&ptr);
+extern void refresh_ptr(int **ptr);
+
+int fncall_reassignment(int *fncall_reassigned) {
+  *fncall_reassigned = 42;
+
+  fncall_reassigned = fncall();
+
+  if (fncall_reassigned) {
+    // CHECK-MESSAGES-NOT: :[[@LINE-1]]:7: warning: pointer value is checked even though it cannot be null at...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/84166


More information about the cfe-commits mailing list