Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[clang][dataflow] Add reverse null checker #1

Closed
wants to merge 1 commit into from
Closed

Conversation

Discookie
Copy link
Owner

@Discookie Discookie commented Oct 18, 2023

TITLE: [clang][dataflow] Add null-check after dereference checker

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#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.


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).

But the checker also found quite a few true positives, on LLVM: 1 2 3 and a couple more, listed here.


This checker corresponds to the the CERT rule EXP34-C, Do not dereference null pointers, example 3.

@github-actions
Copy link

github-actions bot commented Oct 18, 2023

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff 35d771fd4ffd300e527da87b8329bd120b220a83 086c092dedf063d327cc118aac56f6d2345208eb -- clang-tools-extra/clang-tidy/bugprone/ReverseNullCheck.cpp clang-tools-extra/clang-tidy/bugprone/ReverseNullCheck.h clang-tools-extra/test/clang-tidy/checkers/bugprone/reverse-null.cpp clang/include/clang/Analysis/FlowSensitive/Models/ReverseNullModel.h clang/lib/Analysis/FlowSensitive/Models/ReverseNullModel.cpp clang/unittests/Analysis/FlowSensitive/ReverseNullModelTest.cpp clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
View the diff from clang-format here.
diff --git a/clang-tools-extra/clang-tidy/bugprone/ReverseNullCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/ReverseNullCheck.cpp
index 253bfb130..2fdf8b497 100644
--- a/clang-tools-extra/clang-tidy/bugprone/ReverseNullCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/ReverseNullCheck.cpp
@@ -32,7 +32,6 @@ using ast_matchers::MatchFinder;
 using dataflow::ReverseNullDiagnoser;
 using dataflow::ReverseNullModel;
 
-
 static constexpr llvm::StringLiteral FuncID("fun");
 
 static std::optional<ReverseNullDiagnoser::ResultType>
@@ -61,19 +60,21 @@ analyzeFunction(const FunctionDecl &FuncDecl, ASTContext &ASTCtx) {
       std::optional<DataflowAnalysisState<ReverseNullModel::Lattice>>>>
       BlockToOutputState = dataflow::runDataflowAnalysis(
           *Context, Analysis, Env,
-          [&ASTCtx, &Diagnoser, &Diagnostics](
-              const CFGElement &Elt,
-              const DataflowAnalysisState<ReverseNullModel::Lattice>
-                  &State) mutable {
+          [&ASTCtx, &Diagnoser,
+           &Diagnostics](const CFGElement &Elt,
+                         const DataflowAnalysisState<ReverseNullModel::Lattice>
+                             &State) mutable {
             auto EltDiagnostics = Diagnoser.diagnose(ASTCtx, &Elt, State.Env);
-            llvm::move(EltDiagnostics.first, std::back_inserter(Diagnostics.first));
-            llvm::move(EltDiagnostics.second, std::back_inserter(Diagnostics.second));
+            llvm::move(EltDiagnostics.first,
+                       std::back_inserter(Diagnostics.first));
+            llvm::move(EltDiagnostics.second,
+                       std::back_inserter(Diagnostics.second));
           });
 
   // FIXME: Port this over into UncheckedOptionalAccessCheck
   if (llvm::Error E = BlockToOutputState.takeError()) {
-    llvm::dbgs() << "Dataflow analysis failed: "
-        << llvm::toString(std::move(E)) << ".\n";
+    llvm::dbgs() << "Dataflow analysis failed: " << llvm::toString(std::move(E))
+                 << ".\n";
     return std::nullopt;
   }
 
@@ -96,8 +97,7 @@ void ReverseNullCheck::registerMatchers(MatchFinder *Finder) {
       this);
 }
 
-void ReverseNullCheck::check(
-    const MatchFinder::MatchResult &Result) {
+void ReverseNullCheck::check(const MatchFinder::MatchResult &Result) {
   // llvm::errs() << "Starting result parse\n";
   if (Result.SourceManager->getDiagnostics().hasUncompilableErrorOccurred())
     return;
@@ -114,7 +114,8 @@ void ReverseNullCheck::check(
       diag(Loc, "pointer value is checked despite dereferencing it earlier");
 
     for (const SourceLocation &Loc : Errors->first)
-      diag(Loc, "pointer value is checked but it can only be null at this point");
+      diag(Loc,
+           "pointer value is checked but it can only be null at this point");
   }
 }
 
diff --git a/clang-tools-extra/clang-tidy/bugprone/ReverseNullCheck.h b/clang-tools-extra/clang-tidy/bugprone/ReverseNullCheck.h
index 2ab182c9a..4295bae75 100644
--- a/clang-tools-extra/clang-tidy/bugprone/ReverseNullCheck.h
+++ b/clang-tools-extra/clang-tidy/bugprone/ReverseNullCheck.h
@@ -20,7 +20,7 @@ class ReverseNullCheck : public ClangTidyCheck {
 public:
   ReverseNullCheck(StringRef Name, ClangTidyContext *Context)
       : ClangTidyCheck(Name, Context) {}
-  
+
   bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
     return LangOpts.CPlusPlus;
   }
@@ -29,7 +29,7 @@ public:
 };
 
 } // namespace bugprone
-}
-}
+} // namespace tidy
+} // namespace clang
 
 #endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_REVERSENULLCHECK_H
diff --git a/clang/include/clang/Analysis/FlowSensitive/Models/ReverseNullModel.h b/clang/include/clang/Analysis/FlowSensitive/Models/ReverseNullModel.h
index 9ee8a0d28..683cdfcfb 100644
--- a/clang/include/clang/Analysis/FlowSensitive/Models/ReverseNullModel.h
+++ b/clang/include/clang/Analysis/FlowSensitive/Models/ReverseNullModel.h
@@ -29,7 +29,6 @@
 #include "llvm/ADT/StringRef.h"
 #include "llvm/ADT/Twine.h"
 
-
 namespace clang {
 namespace dataflow {
 
@@ -39,13 +38,13 @@ namespace dataflow {
 // invalidate the analysis. It also could be optimized to drop out-of-scope
 // variables from the map.
 class ReverseNullModel
-    : public DataflowAnalysis<ReverseNullModel,
-                              NoopLattice> {
+    : public DataflowAnalysis<ReverseNullModel, NoopLattice> {
 public:
   struct TransferArgs {
     bool Branch;
     Environment &Env;
   };
+
 private:
   CFGMatchSwitch<Environment> TransferMatchSwitch;
   ASTMatchSwitch<Stmt, TransferArgs> BranchTransferMatchSwitch;
@@ -53,30 +52,27 @@ private:
 public:
   explicit ReverseNullModel(ASTContext &Context);
 
-  static NoopLattice initialElement() {
-    return {};
-  }
+  static NoopLattice initialElement() { return {}; }
 
   static ast_matchers::StatementMatcher ptrValueMatcher();
 
   // Used to initialize the storage locations of function arguments.
   // Required to merge these values within the environment.
-  void initializeFunctionParameters(const ControlFlowContext &CFCtx, Environment &Env);
+  void initializeFunctionParameters(const ControlFlowContext &CFCtx,
+                                    Environment &Env);
 
-  void transfer(const CFGElement &E, NoopLattice &State,
-                Environment &Env);
+  void transfer(const CFGElement &E, NoopLattice &State, Environment &Env);
 
   void transferBranch(bool Branch, const Stmt *E, NoopLattice &State,
-                Environment &Env);
+                      Environment &Env);
 
-  bool merge(QualType Type,
-              const Value &Val1, const Environment &Env1,
-              const Value &Val2, const Environment &Env2,
-              Value &MergedVal, Environment &MergedEnv) override;
+  bool merge(QualType Type, const Value &Val1, const Environment &Env1,
+             const Value &Val2, const Environment &Env2, Value &MergedVal,
+             Environment &MergedEnv) override;
 
-  ComparisonResult compare(QualType Type,
-                           const Value &Val1, const Environment &Env1,
-                           const Value &Val2, const Environment &Env2) override;
+  ComparisonResult compare(QualType Type, const Value &Val1,
+                           const Environment &Env1, const Value &Val2,
+                           const Environment &Env2) override;
 
   Value *widen(QualType Type, Value &Prev, const Environment &PrevEnv,
                Value &Current, Environment &CurrentEnv) override;
@@ -85,17 +81,17 @@ public:
 class ReverseNullDiagnoser {
 public:
   ReverseNullDiagnoser();
-  using ResultType = std::pair<std::vector<SourceLocation>, std::vector<SourceLocation>>;
+  using ResultType =
+      std::pair<std::vector<SourceLocation>, std::vector<SourceLocation>>;
 
   ResultType diagnose(ASTContext &Ctx, const CFGElement *Elt,
-                                       const Environment &Env);
+                      const Environment &Env);
 
 private:
-  CFGMatchSwitch<const Environment, ResultType>
-      DiagnoseMatchSwitch;
+  CFGMatchSwitch<const Environment, ResultType> DiagnoseMatchSwitch;
 };
 
-}
-}
+} // namespace dataflow
+} // namespace clang
 
 #endif // CLANG_ANALYSIS_FLOWSENSITIVE_MODELS_REVERSENULLMODEL_H
diff --git a/clang/lib/Analysis/FlowSensitive/Models/ReverseNullModel.cpp b/clang/lib/Analysis/FlowSensitive/Models/ReverseNullModel.cpp
index 3f0a5f31e..e2e9dbe54 100644
--- a/clang/lib/Analysis/FlowSensitive/Models/ReverseNullModel.cpp
+++ b/clang/lib/Analysis/FlowSensitive/Models/ReverseNullModel.cpp
@@ -37,24 +37,19 @@ constexpr char kVar[] = "var"; // FIXME: Make into prvalue, rvalue
 constexpr char kIsNonnull[] = "is-nonnull";
 constexpr char kIsNull[] = "is-null";
 
-enum class SatisfiabilityResult {
-  Nullptr,
-  Top,
-  True,
-  False,
-  Unknown
-};
+enum class SatisfiabilityResult { Nullptr, Top, True, False, Unknown };
 
 using SR = SatisfiabilityResult;
 
-auto ptrToVar(llvm::StringRef VarName = kVar) { 
-  return traverse(TK_IgnoreUnlessSpelledInSource, 
+auto ptrToVar(llvm::StringRef VarName = kVar) {
+  return traverse(TK_IgnoreUnlessSpelledInSource,
                   declRefExpr(hasType(isAnyPointer())).bind(VarName));
 }
-auto namedPtrToVar(llvm::StringRef name) { 
-  return functionDecl(hasDescendant(declRefExpr(allOf(hasType(isAnyPointer()),
-                     hasDeclaration(namedDecl(hasName(name))))).bind(kVar)
-  )); 
+auto namedPtrToVar(llvm::StringRef name) {
+  return functionDecl(
+      hasDescendant(declRefExpr(allOf(hasType(isAnyPointer()),
+                                      hasDeclaration(namedDecl(hasName(name)))))
+                        .bind(kVar)));
 }
 
 auto derefMatcher() {
@@ -72,56 +67,56 @@ auto arrowMatcher() {
 // FIXME: Deliberately simple - a proper check would only match direct ptr casts
 auto castExprMatcher() {
   return castExpr(hasCastKind(CK_PointerToBoolean),
-                  hasSourceExpression(
-                    ptrToVar()))
+                  hasSourceExpression(ptrToVar()))
       .bind(kCond);
 }
 
-auto nullptrMatcher() {
-  return expr(nullPointerConstant()).bind(kVar);
-}
+auto nullptrMatcher() { return expr(nullPointerConstant()).bind(kVar); }
 
 auto addressofMatcher() {
   return unaryOperator(hasOperatorName("&")).bind(kVar);
 }
 
 auto functionCallMatcher() {
-  return callExpr(hasDeclaration(functionDecl(returns(isAnyPointer())))).bind(kVar);
+  return callExpr(hasDeclaration(functionDecl(returns(isAnyPointer()))))
+      .bind(kVar);
 }
 
-auto anyPointerMatcher() {
-  return expr(hasType(isAnyPointer())).bind(kVar);
-}
+auto anyPointerMatcher() { return expr(hasType(isAnyPointer())).bind(kVar); }
 
 // Only computes simple comparisons against boolean True and False values.
 // Faster, but produces many Unknown values.
-SatisfiabilityResult shallowComputeSatisfiability(BoolValue *Val, const Environment &Env) {
+SatisfiabilityResult shallowComputeSatisfiability(BoolValue *Val,
+                                                  const Environment &Env) {
   if (!Val)
     return SR::Nullptr;
-  
+
   if (isa<TopBoolValue>(Val))
     return SR::Top;
 
   if (Val == &Env.getBoolLiteralValue(true))
     return SR::True;
-  
+
   if (Val == &Env.getBoolLiteralValue(false))
     return SR::False;
 
   return SR::Unknown;
 }
 
-// Computes satisfiability by using the flow condition. Slower, but more precise.
-SatisfiabilityResult computeSatisfiability(BoolValue *Val, const Environment &Env) {
+// Computes satisfiability by using the flow condition. Slower, but more
+// precise.
+SatisfiabilityResult computeSatisfiability(BoolValue *Val,
+                                           const Environment &Env) {
   // Early return with the fast compute values.
-  if (SatisfiabilityResult ShallowResult = shallowComputeSatisfiability(Val, Env);
+  if (SatisfiabilityResult ShallowResult =
+          shallowComputeSatisfiability(Val, Env);
       ShallowResult != SR::Unknown) {
     return ShallowResult;
   }
 
   if (Env.flowConditionImplies(Val->formula()))
     return SR::True;
-  
+
   if (Env.flowConditionImplies(Env.arena().makeNot(Val->formula())))
     return SR::False;
 
@@ -134,14 +129,14 @@ inline BoolValue &getVal(llvm::StringRef Name, Value &RootValue) {
 
 void initializeRootValue(Value &RootValue, Environment &Env) {
 
-    Arena &A = Env.arena();
+  Arena &A = Env.arena();
 
-    BoolValue &IsNull = A.makeAtomValue();
-    BoolValue &IsNonnull = A.makeAtomValue();
-    RootValue.setProperty(kIsNull, IsNull);
-    RootValue.setProperty(kIsNonnull, IsNonnull);
+  BoolValue &IsNull = A.makeAtomValue();
+  BoolValue &IsNonnull = A.makeAtomValue();
+  RootValue.setProperty(kIsNull, IsNull);
+  RootValue.setProperty(kIsNonnull, IsNonnull);
 
-    Env.addToFlowCondition(A.makeOr(IsNull.formula(), IsNonnull.formula()));
+  Env.addToFlowCondition(A.makeOr(IsNull.formula(), IsNonnull.formula()));
 }
 
 void setLValue(const Expr &E, Value &Val, Environment &Env) {
@@ -165,40 +160,41 @@ void setUnknownValue(const Expr &E, Value &Val, Environment &Env) {
 }
 
 Value *getValue(const Expr &Var, Environment &Env) {
-    if (auto *EnvVal = Env.getValue(Var)) {
-        // FIXME: Hack
-        if (!EnvVal->getProperty(kIsNull) && !EnvVal->getProperty(kIsNonnull)) {
-            initializeRootValue(*EnvVal, Env);
-        }
-
-        return EnvVal;
+  if (auto *EnvVal = Env.getValue(Var)) {
+    // FIXME: Hack
+    if (!EnvVal->getProperty(kIsNull) && !EnvVal->getProperty(kIsNonnull)) {
+      initializeRootValue(*EnvVal, Env);
     }
 
-    auto *RootValue = Env.createValue(Var.getType());
+    return EnvVal;
+  }
 
-    if (!RootValue) 
-        return nullptr;
+  auto *RootValue = Env.createValue(Var.getType());
 
-    initializeRootValue(*RootValue, Env);
+  if (!RootValue)
+    return nullptr;
+
+  initializeRootValue(*RootValue, Env);
 
-    setLValue(Var, *RootValue, Env);
+  setLValue(Var, *RootValue, Env);
 
-    return RootValue;
+  return RootValue;
 }
 
 void matchDereferenceExpr(const Stmt *stmt,
                           const MatchFinder::MatchResult &Result,
                           Environment &Env) {
-    const auto *Var = Result.Nodes.getNodeAs<Expr>(kVar);
-    assert(Var != nullptr);
+  const auto *Var = Result.Nodes.getNodeAs<Expr>(kVar);
+  assert(Var != nullptr);
 
-    // FIXME: Optimize
-    auto *RootValue = getValue(*Var, Env);
-    if (!RootValue) {
-       return;
-    }
+  // FIXME: Optimize
+  auto *RootValue = getValue(*Var, Env);
+  if (!RootValue) {
+    return;
+  }
 
-    Env.addToFlowCondition(Env.arena().makeNot(getVal(kIsNull, *RootValue).formula()));
+  Env.addToFlowCondition(
+      Env.arena().makeNot(getVal(kIsNull, *RootValue).formula()));
 }
 
 void matchCastExpr(const CastExpr *cond, const MatchFinder::MatchResult &Result,
@@ -211,109 +207,117 @@ void matchCastExpr(const CastExpr *cond, const MatchFinder::MatchResult &Result,
   auto *RootValue = getValue(*Var, Env);
   if (!RootValue) {
     return;
-    }
+  }
 
-    auto *NewRootValue = Env.createValue(Var->getType());
-    if (!NewRootValue) 
-        return;
+  auto *NewRootValue = Env.createValue(Var->getType());
+  if (!NewRootValue)
+    return;
 
-    setLValue(*Var, *NewRootValue, Env);
+  setLValue(*Var, *NewRootValue, Env);
 
-    Arena &A = Env.arena();
+  Arena &A = Env.arena();
 
-    if (IsNonnullBranch) {
-        BoolValue &IsNonnull = getVal(kIsNonnull, *RootValue);
-        BoolValue &IsNull = getVal(kIsNull, *RootValue);
+  if (IsNonnullBranch) {
+    BoolValue &IsNonnull = getVal(kIsNonnull, *RootValue);
+    BoolValue &IsNull = getVal(kIsNull, *RootValue);
 
-        Env.addToFlowCondition(A.makeNot(IsNull.formula()));
-        NewRootValue->setProperty(kIsNull, Env.getBoolLiteralValue(false));
+    Env.addToFlowCondition(A.makeNot(IsNull.formula()));
+    NewRootValue->setProperty(kIsNull, Env.getBoolLiteralValue(false));
 
-        NewRootValue->setProperty(kIsNonnull, IsNonnull);
-    } else {
-        BoolValue &IsNonnull = getVal(kIsNonnull, *RootValue);
-        BoolValue &IsNull = getVal(kIsNull, *RootValue);
+    NewRootValue->setProperty(kIsNonnull, IsNonnull);
+  } else {
+    BoolValue &IsNonnull = getVal(kIsNonnull, *RootValue);
+    BoolValue &IsNull = getVal(kIsNull, *RootValue);
 
-        NewRootValue->setProperty(kIsNull, IsNull);
+    NewRootValue->setProperty(kIsNull, IsNull);
 
-        Env.addToFlowCondition(A.makeNot(IsNonnull.formula()));
-        NewRootValue->setProperty(kIsNonnull, Env.getBoolLiteralValue(false));
-    }
+    Env.addToFlowCondition(A.makeNot(IsNonnull.formula()));
+    NewRootValue->setProperty(kIsNonnull, Env.getBoolLiteralValue(false));
+  }
 }
 
-void matchNullptrExpr(const Expr *expr, const MatchFinder::MatchResult &Result, Environment &Env) {
-    const auto *PrVar = Result.Nodes.getNodeAs<Expr>(kVar);
-    assert(PrVar != nullptr);
+void matchNullptrExpr(const Expr *expr, const MatchFinder::MatchResult &Result,
+                      Environment &Env) {
+  const auto *PrVar = Result.Nodes.getNodeAs<Expr>(kVar);
+  assert(PrVar != nullptr);
 
-    if (Env.getValue(*PrVar))
-      return;
+  if (Env.getValue(*PrVar))
+    return;
 
-    // FIXME: Nullptr does not create a value
-    auto *RootValue = Env.createValue(PrVar->getType());
-    if (!RootValue) {
-       return;
-    }
+  // FIXME: Nullptr does not create a value
+  auto *RootValue = Env.createValue(PrVar->getType());
+  if (!RootValue) {
+    return;
+  }
 
-    RootValue->setProperty(kIsNull, Env.getBoolLiteralValue(true));
-    RootValue->setProperty(kIsNonnull, Env.getBoolLiteralValue(false));
-    Env.setValue(*PrVar, *RootValue);
+  RootValue->setProperty(kIsNull, Env.getBoolLiteralValue(true));
+  RootValue->setProperty(kIsNonnull, Env.getBoolLiteralValue(false));
+  Env.setValue(*PrVar, *RootValue);
 }
 
-void matchAddressofExpr(const Expr *expr, const MatchFinder::MatchResult &Result, Environment &Env) {
-    const auto *PrVar = Result.Nodes.getNodeAs<Expr>(kVar);
-    assert(PrVar != nullptr);
+void matchAddressofExpr(const Expr *expr,
+                        const MatchFinder::MatchResult &Result,
+                        Environment &Env) {
+  const auto *PrVar = Result.Nodes.getNodeAs<Expr>(kVar);
+  assert(PrVar != nullptr);
 
-    auto *RootValue = Env.createValue(PrVar->getType());
-    if (!RootValue) {
-       return;
-    }
+  auto *RootValue = Env.createValue(PrVar->getType());
+  if (!RootValue) {
+    return;
+  }
 
-    RootValue->setProperty(kIsNull, Env.getBoolLiteralValue(false));
-    RootValue->setProperty(kIsNonnull, Env.getBoolLiteralValue(true));
-    Env.setValue(*PrVar, *RootValue);
+  RootValue->setProperty(kIsNull, Env.getBoolLiteralValue(false));
+  RootValue->setProperty(kIsNonnull, Env.getBoolLiteralValue(true));
+  Env.setValue(*PrVar, *RootValue);
 }
 
-void matchFunctionCallExpr(const Expr *fncall, const MatchFinder::MatchResult &Result, Environment &Env) {
-    // This is not necessarily a prvalue, since operators such as prefix ++ are lvalues.
-    const auto *Var = Result.Nodes.getNodeAs<Expr>(kVar);
-    assert(Var != nullptr);
+void matchFunctionCallExpr(const Expr *fncall,
+                           const MatchFinder::MatchResult &Result,
+                           Environment &Env) {
+  // This is not necessarily a prvalue, since operators such as prefix ++ are
+  // lvalues.
+  const auto *Var = Result.Nodes.getNodeAs<Expr>(kVar);
+  assert(Var != nullptr);
 
-    // Initialize to (Unknown, Unknown)
-    if (Env.getValue(*Var))
-      return;
+  // Initialize to (Unknown, Unknown)
+  if (Env.getValue(*Var))
+    return;
 
-    auto *RootValue = Env.createValue(Var->getType());
-    if (!RootValue)
-       return;
-    
-    initializeRootValue(*RootValue, Env);
-    setUnknownValue(*Var, *RootValue, Env);
+  auto *RootValue = Env.createValue(Var->getType());
+  if (!RootValue)
+    return;
+
+  initializeRootValue(*RootValue, Env);
+  setUnknownValue(*Var, *RootValue, Env);
 }
 
 ReverseNullDiagnoser::ResultType
 diagnoseCastExpr(const CastExpr *Stmt, const MatchFinder::MatchResult &Result,
-               const Environment &Env) {
-    const auto *Var = Result.Nodes.getNodeAs<Expr>(kVar);
-    assert(Var != nullptr);
-
-    Arena &A = Env.arena();
-
-    if (auto *RootValue = Env.getValue(*Var)) {
-        // FIXME: Hack
-        if (RootValue->getProperty(kIsNull) && RootValue->getProperty(kIsNonnull)) {
-            bool IsNull = !Env.flowConditionImplies(A.makeNot(getVal(kIsNull, *RootValue).formula()));
-            bool IsNonnull = !Env.flowConditionImplies(A.makeNot(getVal(kIsNonnull, *RootValue).formula()));
-
-            if (!IsNull && IsNonnull) {
-                return {{}, {Var->getBeginLoc()}};
-            }
-
-            if (IsNull && !IsNonnull) {
-                return {{Var->getBeginLoc()}, {}};
-            }
-        }
+                 const Environment &Env) {
+  const auto *Var = Result.Nodes.getNodeAs<Expr>(kVar);
+  assert(Var != nullptr);
+
+  Arena &A = Env.arena();
+
+  if (auto *RootValue = Env.getValue(*Var)) {
+    // FIXME: Hack
+    if (RootValue->getProperty(kIsNull) && RootValue->getProperty(kIsNonnull)) {
+      bool IsNull = !Env.flowConditionImplies(
+          A.makeNot(getVal(kIsNull, *RootValue).formula()));
+      bool IsNonnull = !Env.flowConditionImplies(
+          A.makeNot(getVal(kIsNonnull, *RootValue).formula()));
+
+      if (!IsNull && IsNonnull) {
+        return {{}, {Var->getBeginLoc()}};
+      }
+
+      if (IsNull && !IsNonnull) {
+        return {{Var->getBeginLoc()}, {}};
+      }
     }
+  }
 
-    return {};
+  return {};
 }
 
 auto buildTransferMatchSwitch() {
@@ -334,7 +338,8 @@ auto buildBranchTransferMatchSwitch() {
 }
 
 auto buildDiagnoseMatchSwitch() {
-  return CFGMatchSwitchBuilder<const Environment, ReverseNullDiagnoser::ResultType>()
+  return CFGMatchSwitchBuilder<const Environment,
+                               ReverseNullDiagnoser::ResultType>()
       .CaseOfCFGStmt<CastExpr>(castExprMatcher(), diagnoseCastExpr)
       .Build();
 }
@@ -344,14 +349,14 @@ auto buildDiagnoseMatchSwitch() {
 ReverseNullModel::ReverseNullModel(ASTContext &Context)
     : DataflowAnalysis<ReverseNullModel, NoopLattice>(Context),
       TransferMatchSwitch(buildTransferMatchSwitch()),
-      BranchTransferMatchSwitch(buildBranchTransferMatchSwitch()) {
-}
+      BranchTransferMatchSwitch(buildBranchTransferMatchSwitch()) {}
 
 ast_matchers::StatementMatcher ReverseNullModel::ptrValueMatcher() {
   return ptrToVar();
 }
 
-void ReverseNullModel::initializeFunctionParameters(const ControlFlowContext &CFCtx, Environment &Env) {
+void ReverseNullModel::initializeFunctionParameters(
+    const ControlFlowContext &CFCtx, Environment &Env) {
   const FunctionDecl &FD = cast<FunctionDecl>(CFCtx.getDecl());
 
   for (const auto *Param : FD.parameters()) {
@@ -381,12 +386,12 @@ void ReverseNullModel::transfer(const CFGElement &E, NoopLattice &State,
   TransferMatchSwitch(E, getASTContext(), Env);
 }
 
-void ReverseNullModel::transferBranch(bool Branch, const Stmt *E, NoopLattice &State,
-                                      Environment &Env) {
+void ReverseNullModel::transferBranch(bool Branch, const Stmt *E,
+                                      NoopLattice &State, Environment &Env) {
   if (!E)
     return;
 
-  TransferArgs Args = { Branch, Env };
+  TransferArgs Args = {Branch, Env};
   BranchTransferMatchSwitch(*E, getASTContext(), Args);
 }
 
@@ -395,310 +400,319 @@ bool ReverseNullModel::merge(QualType Type, const Value &Val1,
                              const Environment &Env1, const Value &Val2,
                              const Environment &Env2, Value &MergedVal,
                              Environment &MergedEnv) {
-    if (!Type->isAnyPointerType())
-      return false; // FIXME: Maybe requires true?
-
-    // Alternative attempt: is any branch is true ie. can be a pointer, merging it is also a pointer
-    const auto FastMergeToTrue = [&](llvm::StringRef Name) -> BoolValue & {
-      BoolValue *lhsVar = cast_or_null<BoolValue>(Val1.getProperty(Name));
-      BoolValue *rhsVar = cast_or_null<BoolValue>(Val2.getProperty(Name));
+  if (!Type->isAnyPointerType())
+    return false; // FIXME: Maybe requires true?
 
-      SatisfiabilityResult lhsResult = computeSatisfiability(lhsVar, Env1);
-      SatisfiabilityResult rhsResult = computeSatisfiability(rhsVar, Env2);
+  // Alternative attempt: is any branch is true ie. can be a pointer, merging it
+  // is also a pointer
+  const auto FastMergeToTrue = [&](llvm::StringRef Name) -> BoolValue & {
+    BoolValue *lhsVar = cast_or_null<BoolValue>(Val1.getProperty(Name));
+    BoolValue *rhsVar = cast_or_null<BoolValue>(Val2.getProperty(Name));
 
-      if (lhsResult == SR::Top || rhsResult == SR::Top) 
-        return MergedEnv.makeTopBoolValue();
-      else if (lhsResult == SR::True || rhsResult == SR::True)
-        return MergedEnv.getBoolLiteralValue(true);
-      
-      if (lhsResult == SR::Nullptr && rhsResult == SR::Nullptr)
-        return MergedEnv.makeAtomicBoolValue();
-      else if (lhsResult == SR::False && rhsResult == SR::False)
-        return MergedEnv.getBoolLiteralValue(false);
+    SatisfiabilityResult lhsResult = computeSatisfiability(lhsVar, Env1);
+    SatisfiabilityResult rhsResult = computeSatisfiability(rhsVar, Env2);
 
+    if (lhsResult == SR::Top || rhsResult == SR::Top)
       return MergedEnv.makeTopBoolValue();
-    };
-    
-    // Attempt 1: Merge different values to Top.
-    const auto FastMergeValues = [&](llvm::StringRef Name) -> BoolValue & {
-      BoolValue *lhsVar = cast_or_null<BoolValue>(Val1.getProperty(Name));
-      BoolValue *rhsVar = cast_or_null<BoolValue>(Val2.getProperty(Name));
+    else if (lhsResult == SR::True || rhsResult == SR::True)
+      return MergedEnv.getBoolLiteralValue(true);
 
-      SatisfiabilityResult lhsResult = computeSatisfiability(lhsVar, Env1);
-      SatisfiabilityResult rhsResult = computeSatisfiability(rhsVar, Env2);
+    if (lhsResult == SR::Nullptr && rhsResult == SR::Nullptr)
+      return MergedEnv.makeAtomicBoolValue();
+    else if (lhsResult == SR::False && rhsResult == SR::False)
+      return MergedEnv.getBoolLiteralValue(false);
 
-      if (lhsResult == SR::Top || rhsResult == SR::Top) 
-        return MergedEnv.makeTopBoolValue();
-      
-      if (lhsResult == SR::Nullptr && rhsResult == SR::Nullptr)
-        return MergedEnv.makeAtomicBoolValue();
+    return MergedEnv.makeTopBoolValue();
+  };
 
-      if (lhsResult == SR::True || rhsResult == SR::True)
-        return MergedEnv.getBoolLiteralValue(true);
-      else if (lhsResult == SR::False && rhsResult == SR::False)
-        return MergedEnv.getBoolLiteralValue(false);
+  // Attempt 1: Merge different values to Top.
+  const auto FastMergeValues = [&](llvm::StringRef Name) -> BoolValue & {
+    BoolValue *lhsVar = cast_or_null<BoolValue>(Val1.getProperty(Name));
+    BoolValue *rhsVar = cast_or_null<BoolValue>(Val2.getProperty(Name));
+
+    SatisfiabilityResult lhsResult = computeSatisfiability(lhsVar, Env1);
+    SatisfiabilityResult rhsResult = computeSatisfiability(rhsVar, Env2);
 
+    if (lhsResult == SR::Top || rhsResult == SR::Top)
       return MergedEnv.makeTopBoolValue();
-    };
 
-    // Attempt 2: Slow evaluation, but only produces simple variables.
-    const auto MergeValues = [&](llvm::StringRef Name) -> BoolValue & {
-      BoolValue *lhsVar = cast_or_null<BoolValue>(Val1.getProperty(Name));
-      BoolValue *rhsVar = cast_or_null<BoolValue>(Val2.getProperty(Name));
+    if (lhsResult == SR::Nullptr && rhsResult == SR::Nullptr)
+      return MergedEnv.makeAtomicBoolValue();
 
-      SatisfiabilityResult lhsResult = computeSatisfiability(lhsVar, Env1);
-      SatisfiabilityResult rhsResult = computeSatisfiability(rhsVar, Env2);
+    if (lhsResult == SR::True || rhsResult == SR::True)
+      return MergedEnv.getBoolLiteralValue(true);
+    else if (lhsResult == SR::False && rhsResult == SR::False)
+      return MergedEnv.getBoolLiteralValue(false);
 
-      // Handle special cases.
-      if (lhsResult == SR::Top || rhsResult == SR::Top) {
-        return MergedEnv.makeTopBoolValue();
-      } else if (lhsResult == rhsResult) {
-        switch (lhsResult) {
-          case SR::Nullptr:
-            return MergedEnv.makeAtomicBoolValue();
-          case SR::Top:
-            return *lhsVar;
-          case SR::True:
-            return MergedEnv.getBoolLiteralValue(true);
-          case SR::False:
-            return MergedEnv.getBoolLiteralValue(false);
-          case SR::Unknown:
-            if (MergedEnv.flowConditionImplies(MergedEnv.arena().makeEquals(lhsVar->formula(), rhsVar->formula())))
-              return *lhsVar;
-            
-            return MergedEnv.makeTopBoolValue();
-        }
-      }
+    return MergedEnv.makeTopBoolValue();
+  };
 
-      return MergedEnv.makeTopBoolValue();
-    };
+  // Attempt 2: Slow evaluation, but only produces simple variables.
+  const auto MergeValues = [&](llvm::StringRef Name) -> BoolValue & {
+    BoolValue *lhsVar = cast_or_null<BoolValue>(Val1.getProperty(Name));
+    BoolValue *rhsVar = cast_or_null<BoolValue>(Val2.getProperty(Name));
 
-    // Attempt 3: Merge to (L or R), a more complex variable
-    const auto SlowMergeValues = [&](llvm::StringRef Name) -> BoolValue & {
-      BoolValue *lhsVar = cast_or_null<BoolValue>(Val1.getProperty(Name));
-      BoolValue *rhsVar = cast_or_null<BoolValue>(Val2.getProperty(Name));
+    SatisfiabilityResult lhsResult = computeSatisfiability(lhsVar, Env1);
+    SatisfiabilityResult rhsResult = computeSatisfiability(rhsVar, Env2);
 
-      SatisfiabilityResult lhsResult = computeSatisfiability(lhsVar, Env1);
-      SatisfiabilityResult rhsResult = computeSatisfiability(rhsVar, Env2);
+    // Handle special cases.
+    if (lhsResult == SR::Top || rhsResult == SR::Top) {
+      return MergedEnv.makeTopBoolValue();
+    } else if (lhsResult == rhsResult) {
+      switch (lhsResult) {
+      case SR::Nullptr:
+        return MergedEnv.makeAtomicBoolValue();
+      case SR::Top:
+        return *lhsVar;
+      case SR::True:
+        return MergedEnv.getBoolLiteralValue(true);
+      case SR::False:
+        return MergedEnv.getBoolLiteralValue(false);
+      case SR::Unknown:
+        if (MergedEnv.flowConditionImplies(MergedEnv.arena().makeEquals(
+                lhsVar->formula(), rhsVar->formula())))
+          return *lhsVar;
 
-      // Handle special cases.
-      if (lhsResult == SR::Top || rhsResult == SR::Top) {
         return MergedEnv.makeTopBoolValue();
-      } else if (lhsResult == rhsResult) {
-        switch (lhsResult) {
-          case SR::Nullptr:
-            return MergedEnv.makeAtomicBoolValue();
-          case SR::Top:
-            return *lhsVar;
-          case SR::True:
-            return MergedEnv.getBoolLiteralValue(true);
-          case SR::False:
-            return MergedEnv.getBoolLiteralValue(false);
-          case SR::Unknown:
-            break;
-        }
       }
-      
-      const Formula *mergedLhsVar;
-      const Formula *mergedRhsVar;
+    }
+
+    return MergedEnv.makeTopBoolValue();
+  };
 
-      Arena &A = MergedEnv.arena();
-      const Formula *lhsToken = &A.makeAtomRef(Env1.getFlowConditionToken());
-      const Formula *rhsToken = &A.makeAtomRef(Env2.getFlowConditionToken());
+  // Attempt 3: Merge to (L or R), a more complex variable
+  const auto SlowMergeValues = [&](llvm::StringRef Name) -> BoolValue & {
+    BoolValue *lhsVar = cast_or_null<BoolValue>(Val1.getProperty(Name));
+    BoolValue *rhsVar = cast_or_null<BoolValue>(Val2.getProperty(Name));
 
+    SatisfiabilityResult lhsResult = computeSatisfiability(lhsVar, Env1);
+    SatisfiabilityResult rhsResult = computeSatisfiability(rhsVar, Env2);
 
+    // Handle special cases.
+    if (lhsResult == SR::Top || rhsResult == SR::Top) {
+      return MergedEnv.makeTopBoolValue();
+    } else if (lhsResult == rhsResult) {
       switch (lhsResult) {
-        case SR::Nullptr:
-        case SR::Top: // would have returned earlier
-          mergedLhsVar = nullptr;
-          break;
-        case SR::True:
-          mergedLhsVar = lhsToken;
-          break;
-        case SR::False:
-          mergedLhsVar = &A.makeNot(*lhsToken);
-          break;
-        case SR::Unknown:
-          mergedLhsVar = &A.makeImplies(*lhsToken, lhsVar->formula());
-          break;
+      case SR::Nullptr:
+        return MergedEnv.makeAtomicBoolValue();
+      case SR::Top:
+        return *lhsVar;
+      case SR::True:
+        return MergedEnv.getBoolLiteralValue(true);
+      case SR::False:
+        return MergedEnv.getBoolLiteralValue(false);
+      case SR::Unknown:
+        break;
       }
+    }
 
-      switch (rhsResult) {
-        case SR::Nullptr:
-        case SR::Top:
-          mergedRhsVar = nullptr;
-          break;
-        case SR::True:
-          mergedRhsVar = rhsToken;
-          break;
-        case SR::False:
-          mergedRhsVar = &A.makeNot(*rhsToken);
-          break;
-        case SR::Unknown:
-          mergedRhsVar = &A.makeImplies(*rhsToken, rhsVar->formula());
-          break;
-      }
+    const Formula *mergedLhsVar;
+    const Formula *mergedRhsVar;
+
+    Arena &A = MergedEnv.arena();
+    const Formula *lhsToken = &A.makeAtomRef(Env1.getFlowConditionToken());
+    const Formula *rhsToken = &A.makeAtomRef(Env2.getFlowConditionToken());
+
+    switch (lhsResult) {
+    case SR::Nullptr:
+    case SR::Top: // would have returned earlier
+      mergedLhsVar = nullptr;
+      break;
+    case SR::True:
+      mergedLhsVar = lhsToken;
+      break;
+    case SR::False:
+      mergedLhsVar = &A.makeNot(*lhsToken);
+      break;
+    case SR::Unknown:
+      mergedLhsVar = &A.makeImplies(*lhsToken, lhsVar->formula());
+      break;
+    }
 
-      if (!lhsVar && !rhsVar)
-        return MergedEnv.makeAtomicBoolValue();
-      else if (!lhsVar)
-        return A.makeBoolValue(*mergedRhsVar);
-      else if (!rhsVar)
-        return A.makeBoolValue(*mergedLhsVar);
-      else
-        return A.makeBoolValue(A.makeOr(*mergedLhsVar, *mergedRhsVar));
-    };
+    switch (rhsResult) {
+    case SR::Nullptr:
+    case SR::Top:
+      mergedRhsVar = nullptr;
+      break;
+    case SR::True:
+      mergedRhsVar = rhsToken;
+      break;
+    case SR::False:
+      mergedRhsVar = &A.makeNot(*rhsToken);
+      break;
+    case SR::Unknown:
+      mergedRhsVar = &A.makeImplies(*rhsToken, rhsVar->formula());
+      break;
+    }
 
-    BoolValue &NonnullValue = FastMergeValues(kIsNonnull);
-    BoolValue &NullValue = FastMergeValues(kIsNull);
+    if (!lhsVar && !rhsVar)
+      return MergedEnv.makeAtomicBoolValue();
+    else if (!lhsVar)
+      return A.makeBoolValue(*mergedRhsVar);
+    else if (!rhsVar)
+      return A.makeBoolValue(*mergedLhsVar);
+    else
+      return A.makeBoolValue(A.makeOr(*mergedLhsVar, *mergedRhsVar));
+  };
 
-    MergedVal.setProperty(kIsNonnull, NonnullValue);
-    MergedVal.setProperty(kIsNull, NullValue);
+  BoolValue &NonnullValue = FastMergeValues(kIsNonnull);
+  BoolValue &NullValue = FastMergeValues(kIsNull);
 
-    MergedEnv.addToFlowCondition(MergedEnv.makeOr(NonnullValue, NullValue).formula());
+  MergedVal.setProperty(kIsNonnull, NonnullValue);
+  MergedVal.setProperty(kIsNull, NullValue);
 
-    return true;
+  MergedEnv.addToFlowCondition(
+      MergedEnv.makeOr(NonnullValue, NullValue).formula());
+
+  return true;
 }
 
-ComparisonResult ReverseNullModel::compare(QualType Type,
-        const Value &Val1, const Environment &Env1,
-        const Value &Val2, const Environment &Env2) {
+ComparisonResult ReverseNullModel::compare(QualType Type, const Value &Val1,
+                                           const Environment &Env1,
+                                           const Value &Val2,
+                                           const Environment &Env2) {
 
-    if (!Type->isAnyPointerType())
-      return ComparisonResult::Unknown;
+  if (!Type->isAnyPointerType())
+    return ComparisonResult::Unknown;
 
-    // Attempt 1: Different values by pointer compare Unknown, Top to Different, same to Same.
-    auto FastCompareValues = [&](llvm::StringRef Name) -> ComparisonResult {
-      BoolValue *lhsVar = cast_or_null<BoolValue>(Val1.getProperty(Name));
-      BoolValue *rhsVar = cast_or_null<BoolValue>(Val2.getProperty(Name));
+  // Attempt 1: Different values by pointer compare Unknown, Top to Different,
+  // same to Same.
+  auto FastCompareValues = [&](llvm::StringRef Name) -> ComparisonResult {
+    BoolValue *lhsVar = cast_or_null<BoolValue>(Val1.getProperty(Name));
+    BoolValue *rhsVar = cast_or_null<BoolValue>(Val2.getProperty(Name));
 
-      SatisfiabilityResult lhsResult = shallowComputeSatisfiability(lhsVar, Env1);
-      SatisfiabilityResult rhsResult = shallowComputeSatisfiability(rhsVar, Env2);
+    SatisfiabilityResult lhsResult = shallowComputeSatisfiability(lhsVar, Env1);
+    SatisfiabilityResult rhsResult = shallowComputeSatisfiability(rhsVar, Env2);
 
-      if (lhsResult == SR::Top || rhsResult == SR::Top)
-        return ComparisonResult::Same; // FIXME: Should this be different?
+    if (lhsResult == SR::Top || rhsResult == SR::Top)
+      return ComparisonResult::Same; // FIXME: Should this be different?
 
-      if (lhsResult == SR::Unknown || rhsResult == SR::Unknown)
-        return ComparisonResult::Unknown;
-      
-      if (lhsResult == rhsResult)
-        return ComparisonResult::Same;
+    if (lhsResult == SR::Unknown || rhsResult == SR::Unknown)
+      return ComparisonResult::Unknown;
 
-      return ComparisonResult::Different;
-    };
+    if (lhsResult == rhsResult)
+      return ComparisonResult::Same;
 
-    // Attempt 2: Evaluate values, but different values compare to Unknown.
-    auto CompareValues = [&](llvm::StringRef Name) -> ComparisonResult {
-      BoolValue *lhsVar = cast_or_null<BoolValue>(Val1.getProperty(Name));
-      BoolValue *rhsVar = cast_or_null<BoolValue>(Val2.getProperty(Name));
+    return ComparisonResult::Different;
+  };
 
-      SatisfiabilityResult lhsResult = computeSatisfiability(lhsVar, Env1);
-      SatisfiabilityResult rhsResult = computeSatisfiability(rhsVar, Env2);
+  // Attempt 2: Evaluate values, but different values compare to Unknown.
+  auto CompareValues = [&](llvm::StringRef Name) -> ComparisonResult {
+    BoolValue *lhsVar = cast_or_null<BoolValue>(Val1.getProperty(Name));
+    BoolValue *rhsVar = cast_or_null<BoolValue>(Val2.getProperty(Name));
 
-      if (lhsResult == SR::Top || rhsResult == SR::Top)
-        return ComparisonResult::Same; // FIXME: Should this be different?
+    SatisfiabilityResult lhsResult = computeSatisfiability(lhsVar, Env1);
+    SatisfiabilityResult rhsResult = computeSatisfiability(rhsVar, Env2);
 
-      if (lhsResult == SR::Unknown || rhsResult == SR::Unknown)
-        return ComparisonResult::Unknown;
-      
-      if (lhsResult == rhsResult)
-        return ComparisonResult::Same;
+    if (lhsResult == SR::Top || rhsResult == SR::Top)
+      return ComparisonResult::Same; // FIXME: Should this be different?
 
-      return ComparisonResult::Different;
-    };
+    if (lhsResult == SR::Unknown || rhsResult == SR::Unknown)
+      return ComparisonResult::Unknown;
 
-    ComparisonResult NullComparison = FastCompareValues(kIsNull);
-    ComparisonResult NonnullComparison = FastCompareValues(kIsNonnull);
+    if (lhsResult == rhsResult)
+      return ComparisonResult::Same;
 
-    if (NullComparison == ComparisonResult::Different || NonnullComparison == ComparisonResult::Different)
-      return ComparisonResult::Different;
+    return ComparisonResult::Different;
+  };
 
-    if (NullComparison == ComparisonResult::Unknown || NonnullComparison == ComparisonResult::Unknown)
-      return ComparisonResult::Unknown;
+  ComparisonResult NullComparison = FastCompareValues(kIsNull);
+  ComparisonResult NonnullComparison = FastCompareValues(kIsNonnull);
 
-    return ComparisonResult::Same;
-}
+  if (NullComparison == ComparisonResult::Different ||
+      NonnullComparison == ComparisonResult::Different)
+    return ComparisonResult::Different;
 
-// Different in that it replaces differing boolean values with Top.
-ComparisonResult compareAndReplace(QualType Type,
-        Value &Val1, const Environment &Env1,
-        Value &Val2, Environment &Env2) {
+  if (NullComparison == ComparisonResult::Unknown ||
+      NonnullComparison == ComparisonResult::Unknown)
+    return ComparisonResult::Unknown;
 
-    if (!Type->isAnyPointerType())
-      return ComparisonResult::Unknown;
+  return ComparisonResult::Same;
+}
 
-    // Attempt 1: Different values by pointer compare Unknown, Top to Different, same to Same.
-    auto FastCompareValues = [&](llvm::StringRef Name) -> ComparisonResult {
-      BoolValue *lhsVar = cast_or_null<BoolValue>(Val1.getProperty(Name));
-      BoolValue *rhsVar = cast_or_null<BoolValue>(Val2.getProperty(Name));
+// Different in that it replaces differing boolean values with Top.
+ComparisonResult compareAndReplace(QualType Type, Value &Val1,
+                                   const Environment &Env1, Value &Val2,
+                                   Environment &Env2) {
 
-      SatisfiabilityResult lhsResult = shallowComputeSatisfiability(lhsVar, Env1);
-      SatisfiabilityResult rhsResult = shallowComputeSatisfiability(rhsVar, Env2);
+  if (!Type->isAnyPointerType())
+    return ComparisonResult::Unknown;
 
-      if (lhsResult == SR::Top || rhsResult == SR::Top) {
-        Val2.setProperty(Name, Env2.makeTopBoolValue());
-        return ComparisonResult::Same; // FIXME: Should this be different?
-      }
+  // Attempt 1: Different values by pointer compare Unknown, Top to Different,
+  // same to Same.
+  auto FastCompareValues = [&](llvm::StringRef Name) -> ComparisonResult {
+    BoolValue *lhsVar = cast_or_null<BoolValue>(Val1.getProperty(Name));
+    BoolValue *rhsVar = cast_or_null<BoolValue>(Val2.getProperty(Name));
 
-      if (lhsResult == SR::Unknown || rhsResult == SR::Unknown)
-        return ComparisonResult::Unknown;
-      
-      if (lhsResult == rhsResult)
-        return ComparisonResult::Same;
+    SatisfiabilityResult lhsResult = shallowComputeSatisfiability(lhsVar, Env1);
+    SatisfiabilityResult rhsResult = shallowComputeSatisfiability(rhsVar, Env2);
 
+    if (lhsResult == SR::Top || rhsResult == SR::Top) {
       Val2.setProperty(Name, Env2.makeTopBoolValue());
-      return ComparisonResult::Different;
-    };
+      return ComparisonResult::Same; // FIXME: Should this be different?
+    }
 
-    // Attempt 2: Evaluate values, but different values compare to Unknown.
-    auto CompareValues = [&](llvm::StringRef Name) -> ComparisonResult {
-      BoolValue *lhsVar = cast_or_null<BoolValue>(Val1.getProperty(Name));
-      BoolValue *rhsVar = cast_or_null<BoolValue>(Val2.getProperty(Name));
+    if (lhsResult == SR::Unknown || rhsResult == SR::Unknown)
+      return ComparisonResult::Unknown;
 
-      SatisfiabilityResult lhsResult = computeSatisfiability(lhsVar, Env1);
-      SatisfiabilityResult rhsResult = computeSatisfiability(rhsVar, Env2);
+    if (lhsResult == rhsResult)
+      return ComparisonResult::Same;
 
-      if (lhsResult == SR::Top || rhsResult == SR::Top) {
-        Val2.setProperty(Name, Env2.makeTopBoolValue());
-        return ComparisonResult::Same; // FIXME: Should this be different?
-      }
+    Val2.setProperty(Name, Env2.makeTopBoolValue());
+    return ComparisonResult::Different;
+  };
 
-      if (lhsResult == SR::Unknown || rhsResult == SR::Unknown)
-        return ComparisonResult::Unknown;
-      
-      if (lhsResult == rhsResult)
-        return ComparisonResult::Same;
+  // Attempt 2: Evaluate values, but different values compare to Unknown.
+  auto CompareValues = [&](llvm::StringRef Name) -> ComparisonResult {
+    BoolValue *lhsVar = cast_or_null<BoolValue>(Val1.getProperty(Name));
+    BoolValue *rhsVar = cast_or_null<BoolValue>(Val2.getProperty(Name));
 
+    SatisfiabilityResult lhsResult = computeSatisfiability(lhsVar, Env1);
+    SatisfiabilityResult rhsResult = computeSatisfiability(rhsVar, Env2);
+
+    if (lhsResult == SR::Top || rhsResult == SR::Top) {
       Val2.setProperty(Name, Env2.makeTopBoolValue());
-      return ComparisonResult::Different;
-    };
+      return ComparisonResult::Same; // FIXME: Should this be different?
+    }
+
+    if (lhsResult == SR::Unknown || rhsResult == SR::Unknown)
+      return ComparisonResult::Unknown;
 
-    ComparisonResult NullComparison = FastCompareValues(kIsNull);
-    ComparisonResult NonnullComparison = FastCompareValues(kIsNonnull);
+    if (lhsResult == rhsResult)
+      return ComparisonResult::Same;
 
-    if (NullComparison == ComparisonResult::Different || NonnullComparison == ComparisonResult::Different)
-      return ComparisonResult::Different;
+    Val2.setProperty(Name, Env2.makeTopBoolValue());
+    return ComparisonResult::Different;
+  };
 
-    if (NullComparison == ComparisonResult::Unknown || NonnullComparison == ComparisonResult::Unknown)
-      return ComparisonResult::Unknown;
+  ComparisonResult NullComparison = FastCompareValues(kIsNull);
+  ComparisonResult NonnullComparison = FastCompareValues(kIsNonnull);
+
+  if (NullComparison == ComparisonResult::Different ||
+      NonnullComparison == ComparisonResult::Different)
+    return ComparisonResult::Different;
 
-    return ComparisonResult::Same;
+  if (NullComparison == ComparisonResult::Unknown ||
+      NonnullComparison == ComparisonResult::Unknown)
+    return ComparisonResult::Unknown;
+
+  return ComparisonResult::Same;
 }
 
-Value *ReverseNullModel::widen(QualType Type,
-                               Value &Prev, const Environment &PrevEnv,
-                               Value &Current, Environment &CurrentEnv) {
-    if (!Type->isAnyPointerType()) 
-      return nullptr;
+Value *ReverseNullModel::widen(QualType Type, Value &Prev,
+                               const Environment &PrevEnv, Value &Current,
+                               Environment &CurrentEnv) {
+  if (!Type->isAnyPointerType())
+    return nullptr;
 
-    switch (compareAndReplace(Type, Prev, PrevEnv, Current, CurrentEnv)) {
-      case ComparisonResult::Same:
-        return &Prev;
-      case ComparisonResult::Unknown:
-        return nullptr;
-      case ComparisonResult::Different:
-        return &Current; // FIXME: Replace with Tops in compareAndReplace
-      }
+  switch (compareAndReplace(Type, Prev, PrevEnv, Current, CurrentEnv)) {
+  case ComparisonResult::Same:
+    return &Prev;
+  case ComparisonResult::Unknown:
+    return nullptr;
+  case ComparisonResult::Different:
+    return &Current; // FIXME: Replace with Tops in compareAndReplace
+  }
 }
 
 ReverseNullDiagnoser::ReverseNullDiagnoser()
diff --git a/clang/unittests/Analysis/FlowSensitive/ReverseNullModelTest.cpp b/clang/unittests/Analysis/FlowSensitive/ReverseNullModelTest.cpp
index 6e4148bf4..4d0b933bc 100644
--- a/clang/unittests/Analysis/FlowSensitive/ReverseNullModelTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/ReverseNullModelTest.cpp
@@ -6,7 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 //
-//  This file defines a test for pointer nullability, specifically focused on 
+//  This file defines a test for pointer nullability, specifically focused on
 //  finding invalid dereferences, and unnecessary null-checks. Only a limited
 //  set of operations are currently recognized.
 //
@@ -14,6 +14,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "clang/Analysis/FlowSensitive/Models/ReverseNullModel.h"
 #include "TestingSupport.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/Decl.h"
@@ -28,7 +29,6 @@
 #include "clang/Analysis/FlowSensitive/DataflowLattice.h"
 #include "clang/Analysis/FlowSensitive/MapLattice.h"
 #include "clang/Analysis/FlowSensitive/NoopLattice.h"
-#include "clang/Analysis/FlowSensitive/Models/ReverseNullModel.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/ADT/Twine.h"
 #include "llvm/Support/Error.h"
@@ -61,7 +61,7 @@ constexpr char kBoolInvalid[] = "truefalse";
 constexpr char kBoolUnknown[] = "unknown";
 constexpr char kBoolNullptr[] = "is-nullptr";
 
-std::string debugBoolValue(BoolValue *value, const Environment &Env) { 
+std::string debugBoolValue(BoolValue *value, const Environment &Env) {
   if (value == nullptr) {
     return std::string(kBoolNullptr);
   } else {
@@ -77,46 +77,42 @@ std::string debugBoolValue(BoolValue *value, const Environment &Env) {
 }
 
 auto ptrToVar() { return declRefExpr(hasType(isAnyPointer())).bind(kVar); }
-auto namedPtrToVar(llvm::StringRef name) { 
+auto namedPtrToVar(llvm::StringRef name) {
   return declRefExpr(allOf(hasType(isAnyPointer()),
-                     hasDeclaration(namedDecl(hasName(name)))
-  )).bind(kVar); 
+                           hasDeclaration(namedDecl(hasName(name)))))
+      .bind(kVar);
 }
 
 auto nameToVar(llvm::StringRef name) {
-  return declRefExpr(hasType(isAnyPointer()), hasDeclaration(
-    namedDecl(hasName(name))
-  )).bind(kVar);
+  return declRefExpr(hasType(isAnyPointer()),
+                     hasDeclaration(namedDecl(hasName(name))))
+      .bind(kVar);
 }
 
 auto voidCastMatcher() {
-  return stmt(hasDescendant(castExpr(hasCastKind(CK_ToVoid), 
-    hasSourceExpression(ptrToVar())
-  )));
+  return stmt(hasDescendant(
+      castExpr(hasCastKind(CK_ToVoid), hasSourceExpression(ptrToVar()))));
 }
 
 auto derefMatcher() {
-  return traverse(TK_IgnoreUnlessSpelledInSource,
-    unaryOperator(
-      hasOperatorName("*"),
-      hasUnaryOperand(ptrToVar())
-    )
-  );
+  return traverse(
+      TK_IgnoreUnlessSpelledInSource,
+      unaryOperator(hasOperatorName("*"), hasUnaryOperand(ptrToVar())));
 }
 
 // FIXME: Deliberately simple - a proper check would only match direct ptr casts
 auto castExprMatcher() {
-  return castExpr(hasCastKind(CK_PointerToBoolean), 
-      hasSourceExpression(ptrToVar())).bind(kCond)
-  ;
+  return castExpr(hasCastKind(CK_PointerToBoolean),
+                  hasSourceExpression(ptrToVar()))
+      .bind(kCond);
 }
 
 using ::clang::dataflow::test::AnalysisInputs;
 using ::clang::dataflow::test::AnalysisOutputs;
 using ::clang::dataflow::test::checkDataflow;
 using ::llvm::IsStringMapEntry;
-using ::testing::Pair;
 using ::testing::Args;
+using ::testing::Pair;
 using ::testing::UnorderedElementsAre;
 
 MATCHER_P2(HasNullabilityState, null, nonnull, "") {
@@ -128,14 +124,14 @@ MATCHER_P2(HasNullabilityState, null, nonnull, "") {
              *arg.second) == null;
 }
 
-MATCHER_P3(HoldsVariable, name, output, checks, 
-          ((negation ? "doesn't hold" : "holds") +
+MATCHER_P3(HoldsVariable, name, output, checks,
+           ((negation ? "doesn't hold" : "holds") +
             llvm::StringRef(" a variable in its environment that ") +
-            ::testing::DescribeMatcher<std::pair<Value *, Environment *>>(checks, negation)
-          ).str()) {
+            ::testing::DescribeMatcher<std::pair<Value *, Environment *>>(
+                checks, negation))
+               .str()) {
   auto MatchResults = match(functionDecl(hasDescendant(nameToVar(name))),
-                            *output.Target,
-                            output.ASTCtx);
+                            *output.Target, output.ASTCtx);
   // FIXME: Use gtest functions
   assert(!MatchResults.empty());
 
@@ -143,12 +139,13 @@ MATCHER_P3(HoldsVariable, name, output, checks,
   assert(pointerExpr != nullptr);
 
   const auto *ExprValue = arg.Env.getValue(*pointerExpr);
-  
+
   if (ExprValue == nullptr) {
     return false;
   }
 
-  return ExplainMatchResult(checks, std::pair { ExprValue, &arg.Env }, result_listener);
+  return ExplainMatchResult(checks, std::pair{ExprValue, &arg.Env},
+                            result_listener);
 }
 
 template <typename MatcherFactory>
@@ -166,15 +163,17 @@ void ExpectDataflowResult(llvm::StringRef Code, MatcherFactory Expectations) {
               })
               .withSetupTest([&InitEnv](AnalysisOutputs &AO) {
                 assert(InitEnv && "SetupTest ran at an unexpected time");
-                ReverseNullModel &Analysis = static_cast<ReverseNullModel&>(AO.Analysis);
+                ReverseNullModel &Analysis =
+                    static_cast<ReverseNullModel &>(AO.Analysis);
                 Analysis.initializeFunctionParameters(AO.CFCtx, *InitEnv);
                 return llvm::Error::success();
               })
               .withASTBuildArgs({"-fsyntax-only", "-std=c++17"}),
           /*VerifyResults=*/
-          [&Expectations](const llvm::StringMap<DataflowAnalysisState<
-                              ReverseNullModel::Lattice>> &Results,
-                          const AnalysisOutputs &Output) {
+          [&Expectations](
+              const llvm::StringMap<
+                  DataflowAnalysisState<ReverseNullModel::Lattice>> &Results,
+              const AnalysisOutputs &Output) {
             EXPECT_THAT(Results, Expectations(Output));
           }),
       llvm::Succeeded());
@@ -194,10 +193,12 @@ TEST(ReverseNullTest, DereferenceTypes) {
   )";
   ExpectDataflowResult(Code, [](const AnalysisOutputs &Output) -> auto {
     return UnorderedElementsAre(
-        IsStringMapEntry("p", HoldsVariable("p", Output,
-            HasNullabilityState(kBoolFalse, kBoolTrue))),
-        IsStringMapEntry("q", HoldsVariable("q", Output,
-            HasNullabilityState(kBoolFalse, kBoolTrue))));
+        IsStringMapEntry(
+            "p", HoldsVariable("p", Output,
+                               HasNullabilityState(kBoolFalse, kBoolTrue))),
+        IsStringMapEntry(
+            "q", HoldsVariable("q", Output,
+                               HasNullabilityState(kBoolFalse, kBoolTrue))));
   });
 }
 
@@ -216,9 +217,11 @@ TEST(ReverseNullTest, ConditionalTypes) {
   ExpectDataflowResult(Code, [](const AnalysisOutputs &Output) -> auto {
     return UnorderedElementsAre(
         IsStringMapEntry("p_true", HoldsVariable("p", Output,
-            HasNullabilityState(kBoolFalse, kBoolTrue))),
+                                                 HasNullabilityState(
+                                                     kBoolFalse, kBoolTrue))),
         IsStringMapEntry("p_false", HoldsVariable("p", Output,
-            HasNullabilityState(kBoolTrue, kBoolFalse))));
+                                                  HasNullabilityState(
+                                                      kBoolTrue, kBoolFalse))));
   });
 }
 
@@ -248,17 +251,27 @@ TEST(ReverseNullTest, UnrelatedCondition) {
   ExpectDataflowResult(Code, [](const AnalysisOutputs &Output) -> auto {
     return UnorderedElementsAre(
         IsStringMapEntry("p_b_true", HoldsVariable("p", Output,
-            HasNullabilityState(kBoolFalse, kBoolTrue))),
-        IsStringMapEntry("p_b_false", HoldsVariable("p", Output,
-            HasNullabilityState(kBoolUnknown, kBoolUnknown))),
-        IsStringMapEntry("p_merged", HoldsVariable("p", Output,
-            HasNullabilityState(kBoolUnknown, kBoolUnknown))),
+                                                   HasNullabilityState(
+                                                       kBoolFalse, kBoolTrue))),
+        IsStringMapEntry(
+            "p_b_false",
+            HoldsVariable("p", Output,
+                          HasNullabilityState(kBoolUnknown, kBoolUnknown))),
+        IsStringMapEntry(
+            "p_merged",
+            HoldsVariable("p", Output,
+                          HasNullabilityState(kBoolUnknown, kBoolUnknown))),
         IsStringMapEntry("b_true", HoldsVariable("p", Output,
-            HasNullabilityState(kBoolFalse, kBoolTrue))),
+                                                 HasNullabilityState(
+                                                     kBoolFalse, kBoolTrue))),
         IsStringMapEntry("b_p_true", HoldsVariable("p", Output,
-            HasNullabilityState(kBoolInvalid, kBoolInvalid))), // FIXME
-        IsStringMapEntry("b_p_false", HoldsVariable("p", Output,
-            HasNullabilityState(kBoolInvalid, kBoolInvalid))));
+                                                   HasNullabilityState(
+                                                       kBoolInvalid,
+                                                       kBoolInvalid))), // FIXME
+        IsStringMapEntry(
+            "b_p_false",
+            HoldsVariable("p", Output,
+                          HasNullabilityState(kBoolInvalid, kBoolInvalid))));
   });
 }
 
@@ -287,17 +300,28 @@ TEST(ReverseNullTest, AssignmentOfCommonValues) {
   ExpectDataflowResult(Code, [](const AnalysisOutputs &Output) -> auto {
     return UnorderedElementsAre(
         IsStringMapEntry("p_malloc", HoldsVariable("p", Output,
-            HasNullabilityState(kBoolNullptr, kBoolNullptr))), // FIXME
+                                                   HasNullabilityState(
+                                                       kBoolNullptr,
+                                                       kBoolNullptr))), // FIXME
         IsStringMapEntry("p_true", HoldsVariable("p", Output,
-            HasNullabilityState(kBoolFalse, kBoolTrue))),
+                                                 HasNullabilityState(
+                                                     kBoolFalse, kBoolTrue))),
         IsStringMapEntry("p_false", HoldsVariable("p", Output,
-            HasNullabilityState(kBoolTrue, kBoolFalse))),
-        IsStringMapEntry("p_merge", HoldsVariable("p", Output,
-            HasNullabilityState(kBoolUnknown, kBoolUnknown))),
-        IsStringMapEntry("p_nullptr", HoldsVariable("p", Output,
-            HasNullabilityState(kBoolNullptr, kBoolNullptr))), // FIXME
-        IsStringMapEntry("p_extern", HoldsVariable("p", Output,
-            HasNullabilityState(kBoolUnknown, kBoolUnknown))));
+                                                  HasNullabilityState(
+                                                      kBoolTrue, kBoolFalse))),
+        IsStringMapEntry(
+            "p_merge",
+            HoldsVariable("p", Output,
+                          HasNullabilityState(kBoolUnknown, kBoolUnknown))),
+        IsStringMapEntry(
+            "p_nullptr",
+            HoldsVariable(
+                "p", Output,
+                HasNullabilityState(kBoolNullptr, kBoolNullptr))), // FIXME
+        IsStringMapEntry(
+            "p_extern",
+            HoldsVariable("p", Output,
+                          HasNullabilityState(kBoolUnknown, kBoolUnknown))));
   });
 }
 
@@ -317,9 +341,10 @@ TEST(ReverseNullTest, MergeValues) {
     }
   )";
   ExpectDataflowResult(Code, [](const AnalysisOutputs &Output) -> auto {
-    return UnorderedElementsAre(
-        IsStringMapEntry("p_merge", HoldsVariable("p", Output,
-            HasNullabilityState(kBoolUnknown, kBoolTrue))));
+    return UnorderedElementsAre(IsStringMapEntry(
+        "p_merge",
+        HoldsVariable("p", Output,
+                      HasNullabilityState(kBoolUnknown, kBoolTrue))));
   });
 }
 

@Discookie Discookie force-pushed the reverse-null branch 2 times, most recently from 2bb84a6 to 159c114 Compare October 25, 2023 07:37
Discookie pushed a commit that referenced this pull request Oct 30, 2023
…tePluginObject

After llvm#68052 this function changed from returning
a nullptr with `return {};` to returning Expected and hitting `llvm_unreachable` before it could
do so.

I gather that we're never supposed to call this function, but on Windows we actually do call
this function because `interpreter->CreateScriptedProcessInterface()` returns
`ScriptedProcessInterface` not `ScriptedProcessPythonInterface`. Likely because
`target_sp->GetDebugger().GetScriptInterpreter()` also does not return a Python related class.

The previously XFAILed test crashed with:
```
 # .---command stderr------------
 # | PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace.
 # | Stack dump:
 # | 0.  Program arguments: c:\\users\\tcwg\\david.spickett\\build-llvm\\bin\\lldb-test.exe ir-memory-map C:\\Users\\tcwg\\david.spickett\\build-llvm\\tools\\lldb\\test\\Shell\\Expr\\Output\\TestIRMemoryMapWindows.test.tmp C:\\Users\\tcwg\\david.spickett\\llvm-project\\lldb\\test\\Shell\\Expr/Inputs/ir-memory-map-basic
 # | 1.  HandleCommand(command = "run")
 # | Exception Code: 0xC000001D
 # | #0 0x00007ff696b5f588 lldb_private::ScriptedProcessInterface::CreatePluginObject(class llvm::StringRef, class lldb_private::ExecutionContext &, class std::shared_ptr<class lldb_private::StructuredData::Dictionary>, class lldb_private::StructuredData::Generic *) C:\Users\tcwg\david.spickett\llvm-project\lldb\include\lldb\Interpreter\Interfaces\ScriptedProcessInterface.h:28:0
 # | #1 0x00007ff696b1d808 llvm::Expected<std::shared_ptr<lldb_private::StructuredData::Generic> >::operator bool C:\Users\tcwg\david.spickett\llvm-project\llvm\include\llvm\Support\Error.h:567:0
 # | llvm#2 0x00007ff696b1d808 lldb_private::ScriptedProcess::ScriptedProcess(class std::shared_ptr<class lldb_private::Target>, class std::shared_ptr<class lldb_private::Listener>, class lldb_private::ScriptedMetadata const &, class lldb_private::Status &) C:\Users\tcwg\david.spickett\llvm-project\lldb\source\Plugins\Process\scripted\ScriptedProcess.cpp:115:0
 # | llvm#3 0x00007ff696b1d124 std::shared_ptr<lldb_private::ScriptedProcess>::shared_ptr C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.35.32124\include\memory:1478:0
 # | llvm#4 0x00007ff696b1d124 lldb_private::ScriptedProcess::CreateInstance(class std::shared_ptr<class lldb_private::Target>, class std::shared_ptr<class lldb_private::Listener>, class lldb_private::FileSpec const *, bool) C:\Users\tcwg\david.spickett\llvm-project\lldb\source\Plugins\Process\scripted\ScriptedProcess.cpp:61:0
 # | llvm#5 0x00007ff69699c8f4 std::_Ptr_base<lldb_private::Process>::_Move_construct_from C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.35.32124\include\memory:1237:0
 # | llvm#6 0x00007ff69699c8f4 std::shared_ptr<lldb_private::Process>::shared_ptr C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.35.32124\include\memory:1534:0
 # | llvm#7 0x00007ff69699c8f4 std::shared_ptr<lldb_private::Process>::operator= C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.35.32124\include\memory:1594:0
 # | llvm#8 0x00007ff69699c8f4 lldb_private::Process::FindPlugin(class std::shared_ptr<class lldb_private::Target>, class llvm::StringRef, class std::shared_ptr<class lldb_private::Listener>, class lldb_private::FileSpec const *, bool) C:\Users\tcwg\david.spickett\llvm-project\lldb\source\Target\Process.cpp:396:0
 # | llvm#9 0x00007ff6969bd708 std::_Ptr_base<lldb_private::Process>::_Move_construct_from C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.35.32124\include\memory:1237:0
 # | llvm#10 0x00007ff6969bd708 std::shared_ptr<lldb_private::Process>::shared_ptr C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.35.32124\include\memory:1534:0
 # | llvm#11 0x00007ff6969bd708 std::shared_ptr<lldb_private::Process>::operator= C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.35.32124\include\memory:1594:0
 # | llvm#12 0x00007ff6969bd708 lldb_private::Target::CreateProcess(class std::shared_ptr<class lldb_private::Listener>, class llvm::StringRef, class lldb_private::FileSpec const *, bool) C:\Users\tcwg\david.spickett\llvm-project\lldb\source\Target\Target.cpp:215:0
 # | llvm#13 0x00007ff696b13af0 std::_Ptr_base<lldb_private::Process>::_Ptr_base C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.35.32124\include\memory:1230:0
 # | llvm#14 0x00007ff696b13af0 std::shared_ptr<lldb_private::Process>::shared_ptr C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.35.32124\include\memory:1524:0
 # | llvm#15 0x00007ff696b13af0 lldb_private::PlatformWindows::DebugProcess(class lldb_private::ProcessLaunchInfo &, class lldb_private::Debugger &, class lldb_private::Target &, class lldb_private::Status &) C:\Users\tcwg\david.spickett\llvm-project\lldb\source\Plugins\Platform\Windows\PlatformWindows.cpp:495:0
 # | llvm#16 0x00007ff6969cf590 std::_Ptr_base<lldb_private::Process>::_Move_construct_from C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.35.32124\include\memory:1237:0
 # | llvm#17 0x00007ff6969cf590 std::shared_ptr<lldb_private::Process>::shared_ptr C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.35.32124\include\memory:1534:0
 # | llvm#18 0x00007ff6969cf590 std::shared_ptr<lldb_private::Process>::operator= C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.35.32124\include\memory:1594:0
 # | llvm#19 0x00007ff6969cf590 lldb_private::Target::Launch(class lldb_private::ProcessLaunchInfo &, class lldb_private::Stream *) C:\Users\tcwg\david.spickett\llvm-project\lldb\source\Target\Target.cpp:3274:0
 # | llvm#20 0x00007ff696fff82c CommandObjectProcessLaunch::DoExecute(class lldb_private::Args &, class lldb_private::CommandReturnObject &) C:\Users\tcwg\david.spickett\llvm-project\lldb\source\Commands\CommandObjectProcess.cpp:258:0
 # | llvm#21 0x00007ff696fab6c0 lldb_private::CommandObjectParsed::Execute(char const *, class lldb_private::CommandReturnObject &) C:\Users\tcwg\david.spickett\llvm-project\lldb\source\Interpreter\CommandObject.cpp:751:0
 # `-----------------------------
 # error: command failed with exit status: 0xc000001d
```

That might be a bug on the Windows side, or an artifact of how our build is setup,
but whatever it is, having `CreatePluginObject` return an error and
the caller check it, fixes the failing test.

The built lldb can run the script command to use Python, but I'm not sure if that means
anything.
@Discookie Discookie marked this pull request as draft November 2, 2023 08:39
@Discookie Discookie force-pushed the reverse-null branch 2 times, most recently from 9185f98 to 298cc6f Compare November 2, 2023 09:30
@whisperity
Copy link

I will detail my high-level comments first before delving into the nitty-gritty of the source code. At face value, and immediately looking at the very first line in the header, I got slapped in the face with the check's name as it stands right now. I really, really do not like it... If I only go off of the check's name and search Google for reverse null, the first possibly related idea (REVERSE_INULL False Positive “Dereference before NULL check" at Synopsys Coverity Community) in the result list is at the 7th position. Granted, other search engines like DDG or Bing show in 1st or 2nd location the "what is difference between REVERSE_INULL and FORWARD_NULL error in coverity scan(static code analysis)?" at Stack Overflow. It begs the question what does I mean in INULL anyway...??? Note, the only answer here is a guy who admittedly "used to work for Synopsys". Also, even with DDG and Bing, most of the results are completely derailing, unrelated things like operator ?./?[] (something I really love, btw...) in C# and whatnot.

The problem here is that to understand from the general name of the check what we are trying to achieve requires either:

  • knowing the related Coverity analysis rule
  • knowing the theoretical details of dataflow-based analyses

Both of these would mean the user is already seasoned in static analysis. They likely are not, especially when it comes to people who just drive Clang-Tidy through things like coc-clangd or CLion.

Thus, I would like to say that we should give some consideration to the check's name. I think a longer but more informative name (to everyday developers not knowledgeable about this field) would be preferable to a concise but very internal name. Something along the lines of bugprone-null-check-after-dereference? That could capture the meaning nicely.


I think we should consider whether this is truly just bug_prone_ in the first place. Assuming the pointer was indeed null, if you have already dereferenced it by the time you reached the condition, you are definitely not null, because otherwise the program "should have" crashed, right? Or am I missing something and this is a too simple overview?

Copy link

@whisperity whisperity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partial review for now. Unfortunately, I need to leave my work now, I will likely continue from another location tomorrow, and I do not know whether GitHub saves pending reviews clientside or serverside...

@@ -0,0 +1,126 @@

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✏️ (Style nit: stale empty line.)

Comment on lines 10 to 11
// finding invalid dereferences, and unnecessary null-checks. Only a limited
// set of operations are currently recognized.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Nit: It would be beneficial if this "limited set" was explained. What important bits that are meaningfully missing?)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will be documented.

Comment on lines 30 to 32
namespace clang {
namespace tidy {
namespace bugprone {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✏️ (Style nit: LLVM is now in C++17, so nested namespace specifiers namespace clang::tidy::bugprone could be used instead.)


constexpr char kBoolTrue[] = "true";
constexpr char kBoolFalse[] = "false";
constexpr char kBoolInvalid[] = "truefalse";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Maybe this could spell "invalid" instead. Thinking about truefalse its not immediately apparent whether this means "true AND false" or "true OR false", and those are not the same things.)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

truefalse was just more convenient, but I will rework the debugBoolValue function that uses this.

Comment on lines 79 to 114
auto ptrToVar() { return declRefExpr(hasType(isAnyPointer())).bind(kVar); }
auto namedPtrToVar(llvm::StringRef name) {
return declRefExpr(allOf(hasType(isAnyPointer()),
hasDeclaration(namedDecl(hasName(name)))
)).bind(kVar);
}

auto nameToVar(llvm::StringRef name) {
return declRefExpr(hasType(isAnyPointer()), hasDeclaration(
namedDecl(hasName(name))
)).bind(kVar);
}

auto voidCastMatcher() {
return stmt(hasDescendant(castExpr(hasCastKind(CK_ToVoid),
hasSourceExpression(ptrToVar())
)));
}

auto derefMatcher() {
return traverse(TK_IgnoreUnlessSpelledInSource,
unaryOperator(
hasOperatorName("*"),
hasUnaryOperand(ptrToVar())
)
);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what is the convention for clang/unittests, but similar constructs in Clang-Tidy are usually expressed in the form of static const auto objects allocated directly in the TU for the check's implementation. (For matchers that take a parameter, the AST_MATCHER_P macro creates the appropriate matcher class.)

These factory functions can be simplified:

  • For matchers that do not have parameters and do not require imperative logic, a static const(expr?) auto X = matcher(foo()); object that lives only once can express the needed logic.
  • For matchers that do require imperative logic, AST_MATCHER can create the appropriately named matcher.
  • Similarly, where parameters are needed, AST_MATCHER_P does the same thing.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed it does do the same thing. Ultimately I think instead of redefining matchers here, the ones from the ReverseNullModel itself should be somehow used.

Arena &A = Env.arena();

if (auto *RootValue = Env.getValue(*Var)) {
// FIXME: Hack

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the hack here? What happens if the hack is removed, or how should it disappear?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the other hack.

Comment on lines 309 to 321
if (!IsNull && IsNonnull) {
return {{}, {Var->getBeginLoc()}};
}

if (IsNull && !IsNonnull) {
return {{Var->getBeginLoc()}, {}};
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the semantics of this return value? At the definition of the type alias, it is only said it's a pair of Loc vectors. How is it special if one part of the pair has elements, and how if the other. Is it possible that BOTH vectors will contain an element?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not documented, and will most likely be changed. One is for checking when the value is definitely not-null, and the other is for checking when the value is definitely null.

.CaseOfCFGStmt<Expr>(nullptrMatcher(), matchNullptrExpr)
.CaseOfCFGStmt<Expr>(addressofMatcher(), matchAddressofExpr)
.CaseOfCFGStmt<Expr>(functionCallMatcher(), matchFunctionCallExpr)
.CaseOfCFGStmt<Expr>(anyPointerMatcher(), matchFunctionCallExpr)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is "any pointer" a "function call"?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be named the other way around, fixed.

if (!Type->isAnyPointerType())
return false; // FIXME: Maybe requires true?

// Alternative attempt: is any branch is true ie. can be a pointer, merging it is also a pointer

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be a pointer, or can be a null pointer?
Does "can be a pointer" mean that it is not null?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be non-null.

Comment on lines 502 to 503
default:
llvm_unreachable("all cases covered in switch");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✏️ (My problem with having a default: here is that this kills -Wswitch, but I guess because not all branches of the switch actually return we can't really do any better than this... ☹️)

@whisperity
Copy link

Moreover, I have an architectural question as I just got to where the various alternative heuristics for calculating stuff are implemented. Could we somehow verify that alternates get the same consistent results somehow? If this is not feasible at low-level (i.e., running each alternate and comparing that they report the same value), then perhaps we should keep the alternates until the end so when the checker is ready for real-life testing on projects, we can run different configurations, and compare the user-facing reports from Tidy? Is this a good idea, even, or am I overthinking it? I see somewhere you mentioned things could simply just not conclude (and hey, bugprone-unchecked-optional-access at least used to infinitely hang; it might still do so...), so comparing the results eagerly inside the framework from all alternatives might not be a good idea.

Also, a schematic ASCII art for the lattice would be nice to have. (See the "Why are there 5 values?" question. So far, I don't know.)

@@ -0,0 +1,101 @@
//===-- ReverseNullModel.h --------------------------------------*- C++ -*-===//

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you can find this tomorrow, pending reviews are saved serverside.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yay!

@@ -0,0 +1,723 @@
//===-- ReverseNullModel.h --------------------------------------*- C++ -*-===//

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✏️ (Mismatch between file name and what is written in this comment.)

Copy link

@whisperity whisperity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the high-level idea is convincing. In general, the presentation and the understandability of the code need to be improved. I did this review only by reading what's in the code without going into your external presentations and documents. I'm happy to look at them now, but the code in itself should still be capable of explaining what is going on and why it is going on.

@whisperity
Copy link

I've started running the check in the usual CI, and so far, there were no findings or no errors, but in TinyXML's xmltest.cpp, Clang-Tidy locked up. Almost constant 100% CPU use... I allowed it to go for 30 minutes at this point. To unblock the CI, I killed the job, so we'll be seeing what the results are for other projects...

There were some more hangs with the same symptoms, which I killed after letting the jobs stagnate for about 15-30 minutes, on libwebm (sample.cpp, sample_muxer.cpp, and mkvparser.cpp), Xerces-C (39 out of 348 hung), bitcoin (11 out of 251), protobuf (16 out of 161, but I think 1 of these were an explicit crash!), Qt (it doesn't even matter how many TUs failed; basically everything kept on hanging, so I killed the full analyze job...), Contour (2 out of 180 hung, +2 quick crashes), RCT2 (33 out of 541, but some of these were quick failing crashes). Likely, the analysis of llvm-project will behave similarly to Qt, and not conclude meaningfully in a reasonable time...

Copy link

@whisperity whisperity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed earlier today, if the checker is known to be crashy or hangy, we should preemptively forbid clangd from exercising it, as shown in this patch: llvm#69427

@Discookie Discookie force-pushed the reverse-null branch 3 times, most recently from 4d20f44 to 37d3a78 Compare November 28, 2023 06:13
Discookie pushed a commit that referenced this pull request Dec 4, 2023
…lvm#73463)

Despite CWG2497 not being resolved, it is reasonable to expect the
following code to compile (and which is supported by other compilers)

```cpp
  template<typename T> constexpr T f();
  constexpr int g() { return f<int>(); } // #1
  template<typename T> constexpr T f() { return 123; }
  int k[g()];
  // llvm#2
```

To that end, we eagerly instantiate all referenced specializations of
constexpr functions when they are defined.

We maintain a map of (pattern, [instantiations]) independent of
`PendingInstantiations` to avoid having to iterate that list after each
function definition.

We should apply the same logic to constexpr variables, but I wanted to
keep the PR small.

Fixes llvm#73232
Copy link

@whisperity whisperity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, I think the implementation is now much better understandable (also given the fact that this was the 2nd pass over the code). There are some crucial pain points that still need to be improved, however:

  1. The diagnostic output should be reviewed, especially this "Can we get to 'the' previous dereference?"-esque questions. If not, then at least the Checker's CPP file should contain a comment about this...
  2. The documentation file for the checker has a lot of spotty parts. I tried to give as many comments as I could. There are some conflated terminologies (like safety?) and in general the flow of the explanation is just not a good angle to ease the user into understanding what is going on. We're doing something rather complex and new here (especially this all path / no path "stuff").

I also couldn't find any discussion about the lattice merging strategies. I can't even find my own comment wrt. this, so this might be a significant Mandela effect, but I do remember saying that we should evaluate which strategy is better (if such a comparison can be drawn, even...) and leave only that in the suggested upstream patch. If such comparisons are not possible, then running this or that strategy should be configurable through a checker option. I would still like to have some sort of evaluation even if we can't pronounce a definite "winner".

Comment on lines 1 to 2
//===--- NullCheckAfterDereferenceCheck.cpp - clang-tidy
//--------------------------------===//

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(✏️ Style nit: Something went wrong here with the formatting, the line became too long and split.)


using ast_matchers::MatchFinder;
using dataflow::NullCheckAfterDereferenceModel;
using dataflow::ReverseNullDiagnoser;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ The "reverse null" name remained here. This is not inherently a problem! But perhaps it would be worthwhile to think about renaming this to BackpropagatingNull or ...Nullness instead?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to NullCheckAfterDereference, since this diagnoser is specific to the checker, not to the framework.

Comment on lines 10 to 11
This check identifies potentially unsafe pointer null-checks,
by finding cases where the pointer cannot be null at the location of the check.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐩 Is this true? Why would it be unsafe? The null conditional is redundant. What's unsafe is the unconditional dereference prior to the condition, right?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the first example, I wanted to give the exact warning the checker provides, so later I can expand on the specific edge-cases. Changed unsafe => bugprone where possible though.

if (Env.flowConditionImplies(Val->formula()))
return SR::True;

if (Env.flowConditionImplies(Env.arena().makeNot(Val->formula())))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's actually neat! But it triggers a follow-up question: Here, you mean "analysis" as in the analysis of a single function (as driven by the helper function within the check's implementation), and not the analysis of an entire T.U., right?

*p = 42;

if (p) {
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: pointer value is checked despite dereferencing it earlier [bugprone-experimental-reverse-null]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we know, or can we know this "earlier" location? Here it would be trivial to give a note: to line 8 (*p = 42), but it might not be that trivial in the framework. Is it worthwhile to pursue this, or is it too troublesome and could only be done as a future, separate patch?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a past version of the data-flow framework, storing the location of a previous dereference was possible, but currently I don't think it can be implemented. I do want to get this feature working as well, even if not showing all previous dereferences/assignments, at least showing one, but I haven't found a way to do this yet.

Copy link

@whisperity whisperity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Oh yeah I forgot one thing which would end up being in 12881232 locations: in many places the variable names do not follow established LLVM conventions, e.g. lhsResult instead of LHSResult. This is more of a tedious nit than anything significant.)

@whisperity
Copy link

I put the reverse-null branch into the CI and the build broke because the tests are not passing. I'm retrying with skipped tests. 😋

@whisperity
Copy link

Re #1 (comment)

The analyses re-run in the CI, and I uploaded the results to the usual location, although the "detection status" diff of the reports had gone wrong due to the rename.

Nevertheless, here are the failure statistics. All the tallied failures are due to hung kills. ✅ No crashes! I killed an analysis manually after having observed more than 1 hr of runtime for the file. (CodeChecker analyze SHOULD have a --timeout flag or something like that, but I forgot to configure it.)

  • TinyXML: 1/2
  • libWebM: 2/12
  • Xerces-C: 37/348
  • Bitcoin: 11/251
  • ProtoBuf: 15/161
  • Qt: ❌ The whole job was killed, there is no point executing the checker, everything hangs.
  • Contour: 4/180
  • ACID: ❌ Build failed.
  • OpenRCT2: 20/574
  • LLVM: ❌ The whole job was killed, there is no point executing the checker, everything hangs.

Copy link

@whisperity whisperity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, as the checker is known to hang we should position ourselves with this check immediately in a defensive way and add it to the list of forbidden-by-default checks when running through clangd. Please modify the file in the same way as in this commit: e63ab13.

@Discookie
Copy link
Owner Author

Changed the used merge/compare algos to the non-fast ones. Unit tests don't pass yet, but I'm debugging it.

@whisperity
Copy link

Given the changes in 37d3a78..34bdc9b, I do not have further in-line comments. The high-level comment about detecting and diagnosing the "previous dereference" location is still open.

Copy link
Owner Author

@Discookie Discookie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed the used precision-level within the check, so it would be worth to re-run the tests to see if we can find new results or crashes. Otherwise fixed the inline comments where possible.

I'm going to try a couple more ways to store the previous dereference, hopefully one of them works without too much hassle. I think we could realistically show a single dereference, maybe 2 or 3. However, showing all dereferences would lead to more output lines than a template substitution error.
Maybe there is a better way to visualize the locations of dereferences, but that is a future problem.


using ast_matchers::MatchFinder;
using dataflow::NullCheckAfterDereferenceModel;
using dataflow::ReverseNullDiagnoser;
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to NullCheckAfterDereference, since this diagnoser is specific to the checker, not to the framework.

Comment on lines 10 to 11
This check identifies potentially unsafe pointer null-checks,
by finding cases where the pointer cannot be null at the location of the check.
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the first example, I wanted to give the exact warning the checker provides, so later I can expand on the specific edge-cases. Changed unsafe => bugprone where possible though.

Comment on lines 33 to 35
Pointer dot- and arrow-dereferences are supported. In the future, the checker could
also be extended to find cases where a potentially-null pointer can be dereferenced.
(This case is currently gives noisy results).
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A "potentially null pointer" in this case would mean that we can show a previous location where the pointer's value was definitely null. This is noisy because the check only knows definite pointer values in simpler cases, and thus this specific extension would have even more false negatives.
Removed the "In the future" section altogether.

----------------------------------------

The checker supports assigning various values to pointers, making them null or nonnull.
The annotations ``_nullable`` and ``nonnull`` will be supported in a future version.
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved into a dedicated section.

// arithmetic, null-pointer assignments and _nullable/_nonnull attributes are
// missing as of yet.
//
// FIXME: Port over to the new type of dataflow test infrastructure
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still pending, and I would like to write a couple more simple tests in the new testing infrastructure as well.
(The new infra uses tests where instead of checking for a specific null/nonnull state afterwards, the test marks the expected null-state within the inline comments, leading to more readable tests.)

Comment on lines +77 to +88
llvm::dbgs() << "Dataflow analysis failed: " << llvm::toString(std::move(E))
<< ".\n";
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this information would be useful for the majority of cases, especially not as an on-by-default message. I could implement a checker option to show this message, but at that point the user can also run -dataflow-log and see more information than what we can provide.

Comment on lines 108 to 143
const auto *FuncDecl = Result.Nodes.getNodeAs<FunctionDecl>(FuncID);
if (FuncDecl->isTemplated())
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment on lines 112 to 113
if (std::optional<ReverseNullDiagnoser::ResultType> Errors =
analyzeFunction(*FuncDecl, *Result.Context)) {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can destructure the optional, just not inside the if. Done.

Comment on lines 114 to 164
// FIXME: refine error reporting
for (const SourceLocation &Loc : Errors->second)
// FIXME: link an instance of a deref
diag(Loc, "pointer value is checked despite dereferencing it earlier");

for (const SourceLocation &Loc : Errors->first)
diag(Loc,
"pointer value is checked but it can only be null at this point");
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to the way we have to run the data-flow framework, we're restricted to always matching the top-level function with clang-tidy. These SourceLocations are always distinct from each other, and they each can be treated as a warning separate from all others.
I'm not sure how I should separate the multiple distinct warnings, in a way where it doesn't break the tools.

*p = 42;

if (p) {
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: pointer value is checked despite dereferencing it earlier [bugprone-experimental-reverse-null]
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a past version of the data-flow framework, storing the location of a previous dereference was possible, but currently I don't think it can be implemented. I do want to get this feature working as well, even if not showing all previous dereferences/assignments, at least showing one, but I haven't found a way to do this yet.

@Discookie Discookie force-pushed the reverse-null branch 2 times, most recently from 3f57777 to 61c4051 Compare January 22, 2024 09:32
Discookie pushed a commit that referenced this pull request Jan 22, 2024
…vm#75394)

Calling one of pthread join/detach interceptor on an already
joined/detached thread causes asserts such as:

AddressSanitizer: CHECK failed: sanitizer_thread_arg_retval.cpp:56
"((t)) != (0)" (0x0, 0x0) (tid=1236094)
#0 0x555555634f8b in __asan::CheckUnwind()
compiler-rt/lib/asan/asan_rtl.cpp:69:3
#1 0x55555564e06e in __sanitizer::CheckFailed(char const*, int, char
const*, unsigned long long, unsigned long long)
compiler-rt/lib/sanitizer_common/sanitizer_termination.cpp:86:24
llvm#2 0x5555556491df in __sanitizer::ThreadArgRetval::BeforeJoin(unsigned
long) const
compiler-rt/lib/sanitizer_common/sanitizer_thread_arg_retval.cpp:56:3
llvm#3 0x5555556198ed in Join<___interceptor_pthread_tryjoin_np(void*,
void**)::<lambda()> >
compiler-rt/lib/asan/../sanitizer_common/sanitizer_thread_arg_retval.h:74:26
llvm#4 0x5555556198ed in pthread_tryjoin_np
compiler-rt/lib/asan/asan_interceptors.cpp:311:29

The assert are replaced by error codes.
@Discookie Discookie force-pushed the reverse-null branch 2 times, most recently from df590c5 to 6622f00 Compare January 25, 2024 09:36
@whisperity
Copy link

I just came back to checking this patch. I like the way how the warnings were restructured, I think they should be understandable now, and the [Warning, Note...] sequences will synergise well with Tidy's usual format.

Is the code here the most up-to-date? I think we should soon do a new run in the CI for all the major projects we usually test, just to be able to show something on the upstream review.

And we should move the patch over to upstream review as well.

Discookie pushed a commit that referenced this pull request Feb 13, 2024
…ing bound ops (llvm#80317)

`getDataOperandBaseAddr` retrieve the address of a value when we need to
generate bound operations. When switching to HLFIR, we did not really
handle the fact that this value was then pointing to the result of a
hlfir.declare. Because of that the `#1` value was being used. `#0` value
is carrying the correct information about lowerbounds and should be
used. This patch updates the `getDataOperandBaseAddr` function to use
the correct result value from hlfir.declare.
Discookie pushed a commit that referenced this pull request Feb 13, 2024
…ass template explict specializations (llvm#78720)

According to [[dcl.type.elab]
p2](http://eel.is/c++draft/dcl.type.elab#2):
> If an
[elaborated-type-specifier](http://eel.is/c++draft/dcl.type.elab#nt:elaborated-type-specifier)
is the sole constituent of a declaration, the declaration is ill-formed
unless it is an explicit specialization, an explicit instantiation or it
has one of the following forms [...]

Consider the following:
```cpp
template<typename T>
struct A 
{
    template<typename U>
    struct B;
};

template<>
template<typename U>
struct A<int>::B; // #1
```
The _elaborated-type-specifier_ at `#1` declares an explicit
specialization (which is itself a template). We currently (incorrectly)
reject this, and this PR fixes that.

I moved the point at which _elaborated-type-specifiers_ with
_nested-name-specifiers_ are diagnosed from `ParsedFreeStandingDeclSpec`
to `ActOnTag` for two reasons: `ActOnTag` isn't called for explicit
instantiations and partial/explicit specializations, and because it's
where we determine if a member specialization is being declared.

With respect to diagnostics, I am currently issuing the diagnostic
without marking the declaration as invalid or returning early, which
results in more diagnostics that I think is necessary. I would like
feedback regarding what the "correct" behavior should be here.
@Discookie
Copy link
Owner Author

It is now the latest version, ready to test.

@Discookie
Copy link
Owner Author

(As soon as the commit tree syncs anyways.)

@whisperity
Copy link

It is now the latest version, ready to test.

I fired up b9fba0444d4ffbb35548fa7dd546157a6160c3c7 in the CI. No issues on any of the C projects so far, but I think this was intended that the check is incapable of dealing with C, so yeah...

I'll come back with the results and the hangs after C++ analyses succeeded. What I can see now is that xmltest.cpp in TinyXML is one of the usual suspects...

@whisperity
Copy link

(As soon as the commit tree syncs anyways.)

@Discookie I'm not convinced it will do that without a nudge. Next time you update the patch let's take care and do a separate push to master first and then to the PR branch, maybe it will not get into this botched state in that case.


Alright, the analysis concluded over C++ projects. I actually restarted the job (with --timeout 1800, aka. 30 minutes) and specified only the C++ projects from the test set, the grand total time of the full measurement job was 2 d 17 hr. I uploaded the results to the Demo server.

I observed no crashes at all. So all the failures you can see on the run list are from timeouts.

The 30-minute timeout might have been a bit eager, but at least we know that feature works in CodeChecker. This is a per-process timeout, so every time a TU hung for 30 minutes, the Tidy process got terminated. Observing the CI logs (it's CSA-measurements-driver#2923 if you want to check) it seemed that for TUs that do not hang, the analyses execute in a sufficiently dynamic fashion, so maybe a 5-minute timeout would be a more reasonable cut-off for the next test. (I just tried to simulate what I did manually in the past, killing processes after roughly 30 min of runtime... I didn't know about --timeout back then.)

In general, most projects succeeded appropriately. Performance was the worst on Qt, where only 172 TUs out of the 1175 succeeded, taking a whopping 34 hours to execute. LLVM was the second worst (it being on the stand isn't surprising, but Qt lapping it, at first glance, is...) with 23:13 runtime and 687 out of the 3089 jobs failing.

There are quite some new reports, almost 10 per project, except for LLVM, where there were previously no reports but now there are 69.


Potential false positives

Here are some of the interesting findings which I think should be mentioned in the documentation (and preferably also with a test with FIXME:) at least. I have not looked at EVERY report!

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

ParseTrackEntry(..., Track*&, ...) takes the pointer by reference so it could have been set to a non-null in that function. So saying the if is redundant because the value was invited to a NULL earlier but there is a potentially mutating call in between.

Dittos

True positives

Strange reports

@Discookie
Copy link
Owner Author

Hm. I thought to check for function calls that return pointers, but not for functions that take pointers as arguments. Will implement.

The strange report is not that strange, but I don't know how better to display it without spans. In the constructor a call is made with &(diagObj->...) as its argument, and this explicitly dereferences diagObj before the function starts.

@whisperity
Copy link

The strange report is not that strange, but I don't know how better to display it without spans. In the constructor a call is made with &(diagObj->...) as its argument, and this explicitly dereferences diagObj before the function starts.

I might have linked the wrong one there. That one with the ctor was easy to understand I found another one that was strange.

Anyway the reports are up on the server, you can go through them one by one. But most of them is the T*& case.

Discookie pushed a commit that referenced this pull request Mar 4, 2024
The concurrent tests all do a pthread_join at the end, and
concurrent_base.py stops after that pthread_join and sanity checks that
only 1 thread is running. On macOS, after pthread_join() has completed,
there can be an extra thread still running which is completing the
details of that task asynchronously; this causes testsuite failures.
When this happens, we see the second thread is in

```
frame #0: 0x0000000180ce7700 libsystem_kernel.dylib`__ulock_wake + 8
frame #1: 0x0000000180d25ad4 libsystem_pthread.dylib`_pthread_joiner_wake + 52
frame llvm#2: 0x0000000180d23c18 libsystem_pthread.dylib`_pthread_terminate + 384
frame llvm#3: 0x0000000180d23a98 libsystem_pthread.dylib`_pthread_terminate_invoke + 92
frame llvm#4: 0x0000000180d26740 libsystem_pthread.dylib`_pthread_exit + 112
frame llvm#5: 0x0000000180d26040 libsystem_pthread.dylib`_pthread_start + 148
```

there are none of the functions from the test file present on this
thread.

In this patch, instead of counting the number of threads, I iterate over
the threads looking for functions from our test file (by name) and only
count threads that have at least one of them.

It's a lower frequency failure than the darwin kernel bug causing an
extra step instruction mach exception when hardware
breakpoint/watchpoints are used, but once I fixed that, this came up as
the next most common failure for these tests.

rdar://110555062
Discookie pushed a commit that referenced this pull request Mar 4, 2024
…lvm#80904)"

This reverts commit b1ac052.

This commit breaks coroutine splitting for non-swift calling convention
functions. In this example:

```ll
; ModuleID = 'repro.ll'
source_filename = "stdlib/test/runtime/test_llcl.mojo"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

@0 = internal constant { i32, i32 } { i32 trunc (i64 sub (i64 ptrtoint (ptr @craSH to i64), i64 ptrtoint (ptr getelementptr inbounds ({ i32, i32 }, ptr @0, i32 0, i32 1) to i64)) to i32), i32 64 }

define dso_local void @af_suspend_fn(ptr %0, i64 %1, ptr %2) #0 {
  ret void
}

define dso_local void @craSH(ptr %0) #0 {
  %2 = call token @llvm.coro.id.async(i32 64, i32 8, i32 0, ptr @0)
  %3 = call ptr @llvm.coro.begin(token %2, ptr null)
  %4 = getelementptr inbounds { ptr, { ptr, ptr }, i64, { ptr, i1 }, i64, i64 }, ptr poison, i32 0, i32 0
  %5 = call ptr @llvm.coro.async.resume()
  store ptr %5, ptr %4, align 8
  %6 = call { ptr, ptr, ptr } (i32, ptr, ptr, ...) @llvm.coro.suspend.async.sl_p0p0p0s(i32 0, ptr %5, ptr @ctxt_proj_fn, ptr @af_suspend_fn, ptr poison, i64 -1, ptr poison)
  ret void
}

define dso_local ptr @ctxt_proj_fn(ptr %0) #0 {
  ret ptr %0
}

; Function Attrs: nomerge nounwind
declare { ptr, ptr, ptr } @llvm.coro.suspend.async.sl_p0p0p0s(i32, ptr, ptr, ...) #1

; Function Attrs: nounwind
declare token @llvm.coro.id.async(i32, i32, i32, ptr) llvm#2

; Function Attrs: nounwind
declare ptr @llvm.coro.begin(token, ptr writeonly) llvm#2

; Function Attrs: nomerge nounwind
declare ptr @llvm.coro.async.resume() #1

attributes #0 = { "target-features"="+adx,+aes,+avx,+avx2,+bmi,+bmi2,+clflushopt,+clwb,+clzero,+crc32,+cx16,+cx8,+f16c,+fma,+fsgsbase,+fxsr,+invpcid,+lzcnt,+mmx,+movbe,+mwaitx,+pclmul,+pku,+popcnt,+prfchw,+rdpid,+rdpru,+rdrnd,+rdseed,+sahf,+sha,+sse,+sse2,+sse3,+sse4.1,+sse4.2,+sse4a,+ssse3,+vaes,+vpclmulqdq,+wbnoinvd,+x87,+xsave,+xsavec,+xsaveopt,+xsaves" }
attributes #1 = { nomerge nounwind }
attributes llvm#2 = { nounwind }
```

This verifier crashes after the `coro-split` pass with

```
cannot guarantee tail call due to mismatched parameter counts
  musttail call void @af_suspend_fn(ptr poison, i64 -1, ptr poison)
LLVM ERROR: Broken function
PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace.
Stack dump:
0.      Program arguments: opt ../../../reduced.ll -O0
 #0 0x00007f1d89645c0e __interceptor_backtrace.part.0 /build/gcc-11-XeT9lY/gcc-11-11.4.0/build/x86_64-linux-gnu/libsanitizer/asan/../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:4193:28
 #1 0x0000556d94d254f7 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) /home/ubuntu/modular/third-party/llvm-project/llvm/lib/Support/Unix/Signals.inc:723:22
 llvm#2 0x0000556d94d19a2f llvm::sys::RunSignalHandlers() /home/ubuntu/modular/third-party/llvm-project/llvm/lib/Support/Signals.cpp:105:20
 llvm#3 0x0000556d94d1aa42 SignalHandler(int) /home/ubuntu/modular/third-party/llvm-project/llvm/lib/Support/Unix/Signals.inc:371:36
 llvm#4 0x00007f1d88e42520 (/lib/x86_64-linux-gnu/libc.so.6+0x42520)
 llvm#5 0x00007f1d88e969fc __pthread_kill_implementation ./nptl/pthread_kill.c:44:76
 llvm#6 0x00007f1d88e969fc __pthread_kill_internal ./nptl/pthread_kill.c:78:10
 llvm#7 0x00007f1d88e969fc pthread_kill ./nptl/pthread_kill.c:89:10
 llvm#8 0x00007f1d88e42476 gsignal ./signal/../sysdeps/posix/raise.c:27:6
 llvm#9 0x00007f1d88e287f3 abort ./stdlib/abort.c:81:7
 llvm#10 0x0000556d8944be01 std::vector<llvm::json::Value, std::allocator<llvm::json::Value>>::size() const /usr/include/c++/11/bits/stl_vector.h:919:40
 llvm#11 0x0000556d8944be01 bool std::operator==<llvm::json::Value, std::allocator<llvm::json::Value>>(std::vector<llvm::json::Value, std::allocator<llvm::json::Value>> const&, std::vector<llvm::json::Value, std::allocator<llvm::json::Value>> const&) /usr/include/c++/11/bits/stl_vector.h:1893:23
 llvm#12 0x0000556d8944be01 llvm::json::operator==(llvm::json::Array const&, llvm::json::Array const&) /home/ubuntu/modular/third-party/llvm-project/llvm/include/llvm/Support/JSON.h:572:69
 llvm#13 0x0000556d8944be01 llvm::json::operator==(llvm::json::Value const&, llvm::json::Value const&) (.cold) /home/ubuntu/modular/third-party/llvm-project/llvm/lib/Support/JSON.cpp:204:28
 llvm#14 0x0000556d949ed2bd llvm::report_fatal_error(char const*, bool) /home/ubuntu/modular/third-party/llvm-project/llvm/lib/Support/ErrorHandling.cpp:82:70
 llvm#15 0x0000556d8e37e876 llvm::SmallVectorBase<unsigned int>::size() const /home/ubuntu/modular/third-party/llvm-project/llvm/include/llvm/ADT/SmallVector.h:91:32
 llvm#16 0x0000556d8e37e876 llvm::SmallVectorTemplateCommon<llvm::DiagnosticInfoOptimizationBase::Argument, void>::end() /home/ubuntu/modular/third-party/llvm-project/llvm/include/llvm/ADT/SmallVector.h:282:41
 llvm#17 0x0000556d8e37e876 llvm::SmallVector<llvm::DiagnosticInfoOptimizationBase::Argument, 4u>::~SmallVector() /home/ubuntu/modular/third-party/llvm-project/llvm/include/llvm/ADT/SmallVector.h:1215:24
 llvm#18 0x0000556d8e37e876 llvm::DiagnosticInfoOptimizationBase::~DiagnosticInfoOptimizationBase() /home/ubuntu/modular/third-party/llvm-project/llvm/include/llvm/IR/DiagnosticInfo.h:413:7
 llvm#19 0x0000556d8e37e876 llvm::DiagnosticInfoIROptimization::~DiagnosticInfoIROptimization() /home/ubuntu/modular/third-party/llvm-project/llvm/include/llvm/IR/DiagnosticInfo.h:622:7
 llvm#20 0x0000556d8e37e876 llvm::OptimizationRemark::~OptimizationRemark() /home/ubuntu/modular/third-party/llvm-project/llvm/include/llvm/IR/DiagnosticInfo.h:689:7
 llvm#21 0x0000556d8e37e876 operator() /home/ubuntu/modular/third-party/llvm-project/llvm/lib/Transforms/Coroutines/CoroSplit.cpp:2213:14
 llvm#22 0x0000556d8e37e876 emit<llvm::CoroSplitPass::run(llvm::LazyCallGraph::SCC&, llvm::CGSCCAnalysisManager&, llvm::LazyCallGraph&, llvm::CGSCCUpdateResult&)::<lambda()> > /home/ubuntu/modular/third-party/llvm-project/llvm/include/llvm/Analysis/OptimizationRemarkEmitter.h:83:12
 llvm#23 0x0000556d8e37e876 llvm::CoroSplitPass::run(llvm::LazyCallGraph::SCC&, llvm::AnalysisManager<llvm::LazyCallGraph::SCC, llvm::LazyCallGraph&>&, llvm::LazyCallGraph&, llvm::CGSCCUpdateResult&) /home/ubuntu/modular/third-party/llvm-project/llvm/lib/Transforms/Coroutines/CoroSplit.cpp:2212:13
 llvm#24 0x0000556d8c36ecb1 llvm::detail::PassModel<llvm::LazyCallGraph::SCC, llvm::CoroSplitPass, llvm::AnalysisManager<llvm::LazyCallGraph::SCC, llvm::LazyCallGraph&>, llvm::LazyCallGraph&, llvm::CGSCCUpdateResult&>::run(llvm::LazyCallGraph::SCC&, llvm::AnalysisManager<llvm::LazyCallGraph::SCC, llvm::LazyCallGraph&>&, llvm::LazyCallGraph&, llvm::CGSCCUpdateResult&) /home/ubuntu/modular/third-party/llvm-project/llvm/include/llvm/IR/PassManagerInternal.h:91:3
 llvm#25 0x0000556d91c1a84f llvm::PassManager<llvm::LazyCallGraph::SCC, llvm::AnalysisManager<llvm::LazyCallGraph::SCC, llvm::LazyCallGraph&>, llvm::LazyCallGraph&, llvm::CGSCCUpdateResult&>::run(llvm::LazyCallGraph::SCC&, llvm::AnalysisManager<llvm::LazyCallGraph::SCC, llvm::LazyCallGraph&>&, llvm::LazyCallGraph&, llvm::CGSCCUpdateResult&) /home/ubuntu/modular/third-party/llvm-project/llvm/lib/Analysis/CGSCCPassManager.cpp:90:12
 llvm#26 0x0000556d8c3690d1 llvm::detail::PassModel<llvm::LazyCallGraph::SCC, llvm::PassManager<llvm::LazyCallGraph::SCC, llvm::AnalysisManager<llvm::LazyCallGraph::SCC, llvm::LazyCallGraph&>, llvm::LazyCallGraph&, llvm::CGSCCUpdateResult&>, llvm::AnalysisManager<llvm::LazyCallGraph::SCC, llvm::LazyCallGraph&>, llvm::LazyCallGraph&, llvm::CGSCCUpdateResult&>::run(llvm::LazyCallGraph::SCC&, llvm::AnalysisManager<llvm::LazyCallGraph::SCC, llvm::LazyCallGraph&>&, llvm::LazyCallGraph&, llvm::CGSCCUpdateResult&) /home/ubuntu/modular/third-party/llvm-project/llvm/include/llvm/IR/PassManagerInternal.h:91:3
 llvm#27 0x0000556d91c2162d llvm::ModuleToPostOrderCGSCCPassAdaptor::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) /home/ubuntu/modular/third-party/llvm-project/llvm/lib/Analysis/CGSCCPassManager.cpp:278:18
 llvm#28 0x0000556d8c369035 llvm::detail::PassModel<llvm::Module, llvm::ModuleToPostOrderCGSCCPassAdaptor, llvm::AnalysisManager<llvm::Module>>::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) /home/ubuntu/modular/third-party/llvm-project/llvm/include/llvm/IR/PassManagerInternal.h:91:3
 llvm#29 0x0000556d9457abc5 llvm::PassManager<llvm::Module, llvm::AnalysisManager<llvm::Module>>::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) /home/ubuntu/modular/third-party/llvm-project/llvm/include/llvm/IR/PassManager.h:247:20
 llvm#30 0x0000556d8e30979e llvm::CoroConditionalWrapper::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) /home/ubuntu/modular/third-party/llvm-project/llvm/lib/Transforms/Coroutines/CoroConditionalWrapper.cpp:19:74
 llvm#31 0x0000556d8c365755 llvm::detail::PassModel<llvm::Module, llvm::CoroConditionalWrapper, llvm::AnalysisManager<llvm::Module>>::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) /home/ubuntu/modular/third-party/llvm-project/llvm/include/llvm/IR/PassManagerInternal.h:91:3
 llvm#32 0x0000556d9457abc5 llvm::PassManager<llvm::Module, llvm::AnalysisManager<llvm::Module>>::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) /home/ubuntu/modular/third-party/llvm-project/llvm/include/llvm/IR/PassManager.h:247:20
 llvm#33 0x0000556d89818556 llvm::SmallPtrSetImplBase::isSmall() const /home/ubuntu/modular/third-party/llvm-project/llvm/include/llvm/ADT/SmallPtrSet.h:196:33
 llvm#34 0x0000556d89818556 llvm::SmallPtrSetImplBase::~SmallPtrSetImplBase() /home/ubuntu/modular/third-party/llvm-project/llvm/include/llvm/ADT/SmallPtrSet.h:84:17
 llvm#35 0x0000556d89818556 llvm::SmallPtrSetImpl<llvm::AnalysisKey*>::~SmallPtrSetImpl() /home/ubuntu/modular/third-party/llvm-project/llvm/include/llvm/ADT/SmallPtrSet.h:321:7
 llvm#36 0x0000556d89818556 llvm::SmallPtrSet<llvm::AnalysisKey*, 2u>::~SmallPtrSet() /home/ubuntu/modular/third-party/llvm-project/llvm/include/llvm/ADT/SmallPtrSet.h:427:7
 llvm#37 0x0000556d89818556 llvm::PreservedAnalyses::~PreservedAnalyses() /home/ubuntu/modular/third-party/llvm-project/llvm/include/llvm/IR/Analysis.h:109:7
 llvm#38 0x0000556d89818556 llvm::runPassPipeline(llvm::StringRef, llvm::Module&, llvm::TargetMachine*, llvm::TargetLibraryInfoImpl*, llvm::ToolOutputFile*, llvm::ToolOutputFile*, llvm::ToolOutputFile*, llvm::StringRef, llvm::ArrayRef<llvm::PassPlugin>, llvm::ArrayRef<std::function<void (llvm::PassBuilder&)>>, llvm::opt_tool::OutputKind, llvm::opt_tool::VerifierKind, bool, bool, bool, bool, bool, bool, bool) /home/ubuntu/modular/third-party/llvm-project/llvm/tools/opt/NewPMDriver.cpp:532:10
 llvm#39 0x0000556d897e3939 optMain /home/ubuntu/modular/third-party/llvm-project/llvm/tools/opt/optdriver.cpp:737:27
 llvm#40 0x0000556d89455461 main /home/ubuntu/modular/third-party/llvm-project/llvm/tools/opt/opt.cpp:25:33
 llvm#41 0x00007f1d88e29d90 __libc_start_call_main ./csu/../sysdeps/nptl/libc_start_call_main.h:58:16
 llvm#42 0x00007f1d88e29e40 call_init ./csu/../csu/libc-start.c:128:20
 llvm#43 0x00007f1d88e29e40 __libc_start_main ./csu/../csu/libc-start.c:379:5
 llvm#44 0x0000556d897b6335 _start (/home/ubuntu/modular/.derived/third-party/llvm-project/build-relwithdebinfo-asan/bin/opt+0x150c335)
Aborted (core dumped)
Discookie pushed a commit that referenced this pull request Mar 4, 2024
…ter partial ordering when determining primary template (llvm#82417)

Consider the following:
```
struct A {
  static constexpr bool x = true;
};

template<typename T, typename U>
void f(T, U) noexcept(T::y); // #1, error: no member named 'y' in 'A'

template<typename T, typename U>
void f(T, U*) noexcept(T::x); // llvm#2

template<>
void f(A, int*) noexcept; // explicit specialization of llvm#2
```

We currently instantiate the exception specification of all candidate
function template specializations when deducting template arguments for
an explicit specialization, which results in a error despite `#1` not
being selected by partial ordering as the most specialized template.
According to [except.spec] p13:
> An exception specification is considered to be needed when: 
> - [...]
> - the exception specification is compared to that of another
declaration (e.g., an explicit specialization or an overriding virtual
function);

Assuming that "comparing declarations" means "determining whether the
declarations correspond and declare the same entity" (per [basic.scope.scope] p4 and
[basic.link] p11.1, respectively), the exception specification does _not_ need to be
instantiated until _after_ partial ordering, at which point we determine
whether the implicitly instantiated specialization and the explicit
specialization declare the same entity (the determination of whether two
functions/function templates correspond does not consider the exception
specifications).

This patch defers the instantiation of the exception specification until
a single function template specialization is selected via partial
ordering, matching the behavior of GCC, EDG, and
MSVC: see https://godbolt.org/z/Ebb6GTcWE.
@whisperity
Copy link

Alright, I've gone over most of the reports one more time. If something was very hard to understand due to complex code, I skipped it!

The false positives stemming from not modelling out ptrs need at least a test case (marked with a FIXME) and a sentence or two with an example about it in the documentation file! There are a lot of cases like such.

Questionable: 1 (Why is the report there at the parameter loc?) 2 (Fuzzy locations here as well) 3 (How can something that is dereferenced only be null???)

Strange and not understandable: 1 2 3 4 5 (all from the same function) 6 7 8 (Is this because it is a result of an &Obj?)


Some true positives in the LLVM project (albeit older version!): 1 2 3 4 5

These ones might be worthwhile to have some separate patches to fix, which would double as an immediate proof and reasoning for this check!

@Discookie
Copy link
Owner Author

Apparently the questionable reports are because of two reasons. First is that the checker does not put note tags on variable declarations, and the second is that the checker can't check if the note tag is lexically before the warning's location in the current system. The first few strange reports are also caused by this.

Strange 6, 7, and 8 look to be true positives, as &Obj can never be null yep, and in case 7 the pointer is already checked. Note tags are missing from some operations here as well, more note tag locations will be added later.

Copy link

@whisperity whisperity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[This can be closed.]

@Discookie Discookie closed this Mar 6, 2024
Discookie pushed a commit that referenced this pull request May 6, 2024
Builder alerted me to the failing test, attempt #1 in the blind.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants