balazske updated this revision to Diff 418238.
balazske marked 11 inline comments as done.
balazske added a comment.

Address review comments.

- Removed caching of errno-related values from the checker.
- Added note tags.
- Added documentation comments.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D122150/new/

https://reviews.llvm.org/D122150

Files:
  clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
  clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
  clang/lib/StaticAnalyzer/Checkers/ErrnoChecker.cpp
  clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.cpp
  clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.h
  clang/lib/StaticAnalyzer/Checkers/ErrnoTesterChecker.cpp
  clang/test/Analysis/errno-notes.c
  clang/test/Analysis/errno.c

Index: clang/test/Analysis/errno.c
===================================================================
--- clang/test/Analysis/errno.c
+++ clang/test/Analysis/errno.c
@@ -3,6 +3,7 @@
 // RUN:   -analyzer-checker=apiModeling.Errno \
 // RUN:   -analyzer-checker=debug.ExprInspection \
 // RUN:   -analyzer-checker=debug.ErrnoTest \
+// RUN:   -analyzer-checker=alpha.unix.Errno \
 // RUN:   -DERRNO_VAR
 
 // RUN: %clang_analyze_cc1 -verify %s \
@@ -10,8 +11,10 @@
 // RUN:   -analyzer-checker=apiModeling.Errno \
 // RUN:   -analyzer-checker=debug.ExprInspection \
 // RUN:   -analyzer-checker=debug.ErrnoTest \
+// RUN:   -analyzer-checker=alpha.unix.Errno \
 // RUN:   -DERRNO_FUNC
 
+#include "Inputs/system-header-simulator.h"
 #ifdef ERRNO_VAR
 #include "Inputs/errno_var.h"
 #endif
@@ -25,6 +28,26 @@
 int ErrnoTesterChecker_setErrnoIfError();
 int ErrnoTesterChecker_setErrnoIfErrorRange();
 
+// This function simulates the following:
+// * Return 0 and leave 'errno' with undefined value.
+// This is the case of a successful standard function call.
+// For example if 'ftell' returns not -1.
+// * Return 1 and sets 'errno' to a specific error code (1).
+// This is the case of a failed standard function call.
+// The function indicates the failure by a special return value
+// that is returned only at failure.
+// 'errno' can be checked but it is not required.
+// For example if 'ftell' returns -1.
+// * Return 2 and may set errno to a value (actually it does not set it).
+// This is the case of a standard function call where the failure can only be
+// checked by reading from 'errno'. The value of 'errno' is changed by the
+// function only at failure, the user should set 'errno' to 0 before the call
+// (ErrnoChecker does not check for this rule).
+// 'strtol' is an example of this case, if it returns LONG_MIN (or LONG_MAX)
+// This case applies only if LONG_MIN or LONG_MAX is returned,
+// otherwise the first case in this list applies.
+int ErrnoTesterChecker_setErrnoCheckState();
+
 void something();
 
 void test() {
@@ -61,3 +84,107 @@
     clang_analyzer_eval(errno == 1); // expected-warning{{FALSE}} expected-warning{{TRUE}}
   }
 }
+
+void testErrnoCheck0() {
+  // If the function returns a success result code, value of 'errno'
+  // is unspecified and it is unsafe to make any decision with it.
+  // The function did not promise to not change 'errno' if no failure happens.
+  int X = ErrnoTesterChecker_setErrnoCheckState();
+  if (X == 0) {
+    if (errno) { // expected-warning{{An undefined value may be read from 'errno' [alpha.unix.Errno]}}
+    }
+    if (errno) { // no warning for second time (analysis stops at the first warning)
+    }
+  }
+  X = ErrnoTesterChecker_setErrnoCheckState();
+  if (X == 0) {
+    if (errno) { // expected-warning{{An undefined value may be read from 'errno' [alpha.unix.Errno]}}
+    }
+    errno = 0;
+  }
+  X = ErrnoTesterChecker_setErrnoCheckState();
+  if (X == 0) {
+    errno = 0;
+    if (errno) { // no warning after overwritten 'errno'
+    }
+  }
+}
+
+void testErrnoCheck1() {
+  // If the function returns error result code that is out-of-band (not a valid
+  // non-error return value) the value of 'errno' can be checked but it is not
+  // required to do so.
+  int X = ErrnoTesterChecker_setErrnoCheckState();
+  if (X == 1) {
+    if (errno) { // no warning
+    }
+  }
+  X = ErrnoTesterChecker_setErrnoCheckState();
+  if (X == 1) {
+    errno = 0; // no warning
+  }
+}
+
+void testErrnoCheck2() {
+  // If the function returns an in-band error result the value of 'errno' is
+  // required to be checked to verify if error happened.
+  // The same applies to other functions that can indicate failure only by
+  // change of 'errno'.
+  int X = ErrnoTesterChecker_setErrnoCheckState();
+  if (X == 2) {
+    if (errno) {
+    }
+    errno = 0; // no warning after 'errno' was read
+  }
+  X = ErrnoTesterChecker_setErrnoCheckState();
+  if (X == 2) {
+    errno = 0; // expected-warning{{Value of 'errno' was not checked and is overwritten here [alpha.unix.Errno]}}
+    errno = 0;
+  }
+  X = ErrnoTesterChecker_setErrnoCheckState();
+  if (X == 2) {
+    errno = 0; // expected-warning{{Value of 'errno' was not checked and is overwritten here [alpha.unix.Errno]}}
+    if (errno) {
+    }
+  }
+}
+
+void testErrnoCheckUndefinedLoad() {
+  int X = ErrnoTesterChecker_setErrnoCheckState();
+  if (X == 0) {
+    int Y = errno; // expected-warning{{An undefined value may be read from 'errno' [alpha.unix.Errno]}}
+  }
+}
+
+void testErrnoNotCheckedAtSystemCall() {
+  int X = ErrnoTesterChecker_setErrnoCheckState();
+  if (X == 2) {
+    printf("%i", 1); // expected-warning{{Value of 'errno' was not checked and may be overwritten by function 'printf' [alpha.unix.Errno]}}
+    printf("%i", 1); // no warning ('printf' does not change errno state)
+  }
+}
+
+void testErrnoCheckStateInvalidate() {
+  int X = ErrnoTesterChecker_setErrnoCheckState();
+  if (X == 0) {
+    something();
+    if (errno) { // no warning after an invalidating function call
+    }
+  }
+  X = ErrnoTesterChecker_setErrnoCheckState();
+  if (X == 0) {
+    printf("%i", 1);
+    if (errno) { // no warning after an invalidating standard function call
+    }
+  }
+}
+
+void testErrnoCheckStateInvalidate1() {
+  int X = ErrnoTesterChecker_setErrnoCheckState();
+  if (X == 2) {
+    clang_analyzer_eval(errno); // expected-warning{{TRUE}}
+    something();
+    clang_analyzer_eval(errno); // expected-warning{{UNKNOWN}}
+    errno = 0;                  // no warning after invalidation
+  }
+}
Index: clang/test/Analysis/errno-notes.c
===================================================================
--- /dev/null
+++ clang/test/Analysis/errno-notes.c
@@ -0,0 +1,46 @@
+// RUN: %clang_analyze_cc1 -verify -analyzer-output text %s \
+// RUN:   -analyzer-checker=core \
+// RUN:   -analyzer-checker=apiModeling.Errno \
+// RUN:   -analyzer-checker=debug.ExprInspection \
+// RUN:   -analyzer-checker=debug.ErrnoTest \
+// RUN:   -analyzer-checker=alpha.unix.Errno
+
+#include "Inputs/errno_var.h"
+#include "Inputs/system-header-simulator.h"
+
+int ErrnoTesterChecker_setErrnoCheckState();
+
+void something();
+
+void testErrnoCheckUndefRead() {
+  int X = ErrnoTesterChecker_setErrnoCheckState();
+  something();
+  X = ErrnoTesterChecker_setErrnoCheckState(); // expected-note{{Assuming that this function succeeds but sets 'errno' to an unspecified value}}
+  if (X == 0) {                                // expected-note{{'X' is equal to 0}}
+                                               // expected-note@-1{{Taking true branch}}
+    int Y = errno;                             // expected-warning{{An undefined value may be read from 'errno'}}
+                                               // expected-note@-1{{An undefined value may be read from 'errno'}}
+  }
+}
+
+void testErrnoCheckOverwrite() {
+  int X = ErrnoTesterChecker_setErrnoCheckState();
+  something();
+  X = ErrnoTesterChecker_setErrnoCheckState(); // expected-note{{Assuming that this function returns 2. 'errno' should be checked to test for failure}}
+  if (X == 2) {                                // expected-note{{'X' is equal to 2}}
+                                               // expected-note@-1{{Taking true branch}}
+    errno = 0;                                 // expected-warning{{Value of 'errno' was not checked and is overwritten here}}
+                                               // expected-note@-1{{Value of 'errno' was not checked and is overwritten here}}
+  }
+}
+
+void testErrnoCheckOverwriteStdCall() {
+  int X = ErrnoTesterChecker_setErrnoCheckState();
+  something();
+  X = ErrnoTesterChecker_setErrnoCheckState(); // expected-note{{Assuming that this function returns 2. 'errno' should be checked to test for failure}}
+  if (X == 2) {                                // expected-note{{'X' is equal to 2}}
+                                               // expected-note@-1{{Taking true branch}}
+    printf("");                                // expected-warning{{Value of 'errno' was not checked and may be overwritten by function 'printf'}}
+                                               // expected-note@-1{{Value of 'errno' was not checked and may be overwritten by function 'printf'}}
+  }
+}
Index: clang/lib/StaticAnalyzer/Checkers/ErrnoTesterChecker.cpp
===================================================================
--- clang/lib/StaticAnalyzer/Checkers/ErrnoTesterChecker.cpp
+++ clang/lib/StaticAnalyzer/Checkers/ErrnoTesterChecker.cpp
@@ -20,6 +20,7 @@
 
 using namespace clang;
 using namespace ento;
+using namespace errno_modeling;
 
 namespace {
 
@@ -33,6 +34,7 @@
   static void evalSetErrnoIfError(CheckerContext &C, const CallEvent &Call);
   static void evalSetErrnoIfErrorRange(CheckerContext &C,
                                        const CallEvent &Call);
+  static void evalSetErrnoCheckState(CheckerContext &C, const CallEvent &Call);
 
   using EvalFn = std::function<void(CheckerContext &, const CallEvent &)>;
   const CallDescriptionMap<EvalFn> TestCalls{
@@ -41,22 +43,24 @@
       {{"ErrnoTesterChecker_setErrnoIfError", 0},
        &ErrnoTesterChecker::evalSetErrnoIfError},
       {{"ErrnoTesterChecker_setErrnoIfErrorRange", 0},
-       &ErrnoTesterChecker::evalSetErrnoIfErrorRange}};
+       &ErrnoTesterChecker::evalSetErrnoIfErrorRange},
+      {{"ErrnoTesterChecker_setErrnoCheckState", 0},
+       &ErrnoTesterChecker::evalSetErrnoCheckState}};
 };
 
 } // namespace
 
 void ErrnoTesterChecker::evalSetErrno(CheckerContext &C,
                                       const CallEvent &Call) {
-  C.addTransition(errno_modeling::setErrnoValue(
-      C.getState(), C.getLocationContext(), Call.getArgSVal(0)));
+  C.addTransition(setErrnoValue(C.getState(), C.getLocationContext(),
+                                Call.getArgSVal(0), Errno_Irrelevant));
 }
 
 void ErrnoTesterChecker::evalGetErrno(CheckerContext &C,
                                       const CallEvent &Call) {
   ProgramStateRef State = C.getState();
 
-  Optional<SVal> ErrnoVal = errno_modeling::getErrnoValue(State);
+  Optional<SVal> ErrnoVal = getErrnoValue(State);
   assert(ErrnoVal && "Errno value should be available.");
   State =
       State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), *ErrnoVal);
@@ -74,7 +78,7 @@
 
   ProgramStateRef StateFailure = State->BindExpr(
       Call.getOriginExpr(), C.getLocationContext(), SVB.makeIntVal(1, true));
-  StateFailure = errno_modeling::setErrnoValue(StateFailure, C, 11);
+  StateFailure = setErrnoValue(StateFailure, C, 11, Errno_Irrelevant);
 
   C.addTransition(StateSuccess);
   C.addTransition(StateFailure);
@@ -94,13 +98,40 @@
       nullptr, Call.getOriginExpr(), C.getLocationContext(), C.blockCount());
   StateFailure = StateFailure->assume(ErrnoVal, true);
   assert(StateFailure && "Failed to assume on an initial value.");
-  StateFailure = errno_modeling::setErrnoValue(
-      StateFailure, C.getLocationContext(), ErrnoVal);
+  StateFailure = setErrnoValue(StateFailure, C.getLocationContext(), ErrnoVal,
+                               Errno_Irrelevant);
 
   C.addTransition(StateSuccess);
   C.addTransition(StateFailure);
 }
 
+void ErrnoTesterChecker::evalSetErrnoCheckState(CheckerContext &C,
+                                                const CallEvent &Call) {
+  ProgramStateRef State = C.getState();
+  SValBuilder &SVB = C.getSValBuilder();
+
+  ProgramStateRef StateSuccess = State->BindExpr(
+      Call.getOriginExpr(), C.getLocationContext(), SVB.makeIntVal(0, true));
+  StateSuccess = setErrnoState(StateSuccess, Errno_MustNotBeChecked);
+
+  ProgramStateRef StateFailure1 = State->BindExpr(
+      Call.getOriginExpr(), C.getLocationContext(), SVB.makeIntVal(1, true));
+  StateFailure1 = setErrnoValue(StateFailure1, C, 1, Errno_Irrelevant);
+
+  ProgramStateRef StateFailure2 = State->BindExpr(
+      Call.getOriginExpr(), C.getLocationContext(), SVB.makeIntVal(2, true));
+  StateFailure2 = setErrnoValue(StateFailure2, C, 2, Errno_MustBeChecked);
+
+  C.addTransition(StateSuccess,
+                  getErrnoNoteTag(C, "Assuming that this function succeeds but "
+                                     "sets 'errno' to an unspecified value."));
+  C.addTransition(StateFailure1);
+  C.addTransition(
+      StateFailure2,
+      getErrnoNoteTag(C, "Assuming that this function returns 2. 'errno' "
+                         "should be checked to test for failure."));
+}
+
 bool ErrnoTesterChecker::evalCall(const CallEvent &Call,
                                   CheckerContext &C) const {
   const EvalFn *Fn = TestCalls.lookup(Call);
Index: clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.h
===================================================================
--- clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.h
+++ clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.h
@@ -21,16 +21,55 @@
 namespace ento {
 namespace errno_modeling {
 
+enum ErrnoCheckState : unsigned {
+  /// We do not know anything about 'errno'.
+  Errno_Irrelevant = 0,
+
+  /// Value of 'errno' should be checked to find out if a previous function call
+  /// has failed.
+  Errno_MustBeChecked = 1,
+
+  /// Value of 'errno' is not allowed to be read, it can contain an unspecified
+  /// value.
+  Errno_MustNotBeChecked = 2
+};
+
 /// Returns the value of 'errno', if 'errno' was found in the AST.
 llvm::Optional<SVal> getErrnoValue(ProgramStateRef State);
 
+/// Returns the errno check state, \c Errno_Irrelevant if 'errno' was not found
+/// (this is not the only case for that value).
+ErrnoCheckState getErrnoState(ProgramStateRef State);
+
+/// Returns the location that points to the \c MemoryRegion where the 'errno'
+/// value is stored. Returns \c None if 'errno' was not found. Otherwise it
+/// always returns a valid memory region in the system global memory space.
+llvm::Optional<Loc> getErrnoLoc(ProgramStateRef State);
+
 /// Set value of 'errno' to any SVal, if possible.
+/// The errno check state is set always when the 'errno' value is set.
 ProgramStateRef setErrnoValue(ProgramStateRef State,
-                              const LocationContext *LCtx, SVal Value);
+                              const LocationContext *LCtx, SVal Value,
+                              ErrnoCheckState EState);
 
 /// Set value of 'errno' to a concrete (signed) integer, if possible.
+/// The errno check state is set always when the 'errno' value is set.
 ProgramStateRef setErrnoValue(ProgramStateRef State, CheckerContext &C,
-                              uint64_t Value);
+                              uint64_t Value, ErrnoCheckState EState);
+
+/// Set the errno check state, do not modify the errno value.
+ProgramStateRef setErrnoState(ProgramStateRef State, ErrnoCheckState EState);
+
+/// Determine if a `Decl` node related to 'errno'.
+/// This is true if the declaration is the errno variable or a function
+/// that returns a pointer to the 'errno' value (usually the 'errno' macro is
+/// defined with this function). \p D is not required to be a canonical
+/// declaration.
+bool isErrno(const Decl *D);
+
+/// Create a NoteTag that displays the message if the 'errno' memory region is
+/// marked as interesting, and resets the interestingness.
+const NoteTag *getErrnoNoteTag(CheckerContext &C, const std::string &Message);
 
 } // namespace errno_modeling
 } // namespace ento
Index: clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.cpp
===================================================================
--- clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.cpp
+++ clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.cpp
@@ -76,6 +76,8 @@
   return reinterpret_cast<const MemRegion *>(State->get<ErrnoRegion>());
 }
 
+REGISTER_TRAIT_WITH_PROGRAMSTATE(ErrnoState, unsigned)
+
 /// Search for a variable called "errno" in the AST.
 /// Return nullptr if not found.
 static const VarDecl *getErrnoVar(ASTContext &ACtx) {
@@ -147,7 +149,8 @@
         State->getRegion(ErrnoVar, C.getLocationContext());
     assert(ErrnoR && "Memory region should exist for the 'errno' variable.");
     State = State->set<ErrnoRegion>(ErrnoR);
-    State = errno_modeling::setErrnoValue(State, C, 0);
+    State = errno_modeling::setErrnoValue(State, C, 0,
+                                          errno_modeling::Errno_Irrelevant);
     C.addTransition(State);
   } else if (ErrnoDecl) {
     assert(isa<FunctionDecl>(ErrnoDecl) && "Invalid errno location function.");
@@ -174,7 +177,8 @@
         ACtx.IntTy, SVB.makeZeroArrayIndex(),
         RMgr.getSymbolicRegion(Sym, GlobalSystemSpace), C.getASTContext());
     State = State->set<ErrnoRegion>(ErrnoR);
-    State = errno_modeling::setErrnoValue(State, C, 0);
+    State = errno_modeling::setErrnoValue(State, C, 0,
+                                          errno_modeling::Errno_Irrelevant);
     C.addTransition(State);
   }
 }
@@ -218,22 +222,66 @@
 }
 
 ProgramStateRef setErrnoValue(ProgramStateRef State,
-                              const LocationContext *LCtx, SVal Value) {
+                              const LocationContext *LCtx, SVal Value,
+                              ErrnoCheckState EState) {
   const MemRegion *ErrnoR = getErrnoRegion(State);
   if (!ErrnoR)
     return State;
-  return State->bindLoc(loc::MemRegionVal{ErrnoR}, Value, LCtx);
+  // First set the errno value, the old state is still available at 'checkBind'
+  // or 'checkLocation' for errno value.
+  State = State->bindLoc(loc::MemRegionVal{ErrnoR}, Value, LCtx);
+  return State->set<ErrnoState>(EState);
 }
 
 ProgramStateRef setErrnoValue(ProgramStateRef State, CheckerContext &C,
-                              uint64_t Value) {
+                              uint64_t Value, ErrnoCheckState EState) {
   const MemRegion *ErrnoR = getErrnoRegion(State);
   if (!ErrnoR)
     return State;
-  return State->bindLoc(
+  State = State->bindLoc(
       loc::MemRegionVal{ErrnoR},
       C.getSValBuilder().makeIntVal(Value, C.getASTContext().IntTy),
       C.getLocationContext());
+  return State->set<ErrnoState>(EState);
+}
+
+Optional<Loc> getErrnoLoc(ProgramStateRef State) {
+  const MemRegion *ErrnoR = getErrnoRegion(State);
+  if (!ErrnoR)
+    return {};
+  return loc::MemRegionVal{ErrnoR};
+}
+
+ProgramStateRef setErrnoState(ProgramStateRef State, ErrnoCheckState EState) {
+  return State->set<ErrnoState>(EState);
+}
+
+ErrnoCheckState getErrnoState(ProgramStateRef State) {
+  if (unsigned EState = State->get<ErrnoState>())
+    return static_cast<ErrnoCheckState>(EState);
+  return Errno_Irrelevant;
+}
+
+bool isErrno(const Decl *D) {
+  if (const auto *VD = dyn_cast_or_null<VarDecl>(D))
+    return VD->getIdentifier() &&
+           VD->getIdentifier()->getName() == ErrnoVarName;
+  if (const auto *FD = dyn_cast_or_null<FunctionDecl>(D))
+    if (IdentifierInfo *II = FD->getIdentifier())
+      return llvm::find(ErrnoLocationFuncNames, II->getName()) !=
+             std::end(ErrnoLocationFuncNames);
+  return false;
+}
+
+const NoteTag *getErrnoNoteTag(CheckerContext &C, const std::string &Message) {
+  return C.getNoteTag([Message](PathSensitiveBugReport &BR) {
+    const MemRegion *ErrnoR = getErrnoRegion(BR.getErrorNode()->getState());
+    if (ErrnoR && BR.isInteresting(ErrnoR)) {
+      BR.markNotInteresting(ErrnoR);
+      return Message.c_str();
+    }
+    return "";
+  });
 }
 
 } // namespace errno_modeling
Index: clang/lib/StaticAnalyzer/Checkers/ErrnoChecker.cpp
===================================================================
--- /dev/null
+++ clang/lib/StaticAnalyzer/Checkers/ErrnoChecker.cpp
@@ -0,0 +1,196 @@
+//=== ErrnoChecker.cpp ------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This defines an "errno checker" that can detect some invalid use of the
+// system-defined value 'errno'. This checker works together with the
+// ErrnoModeling checker.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ErrnoModeling.h"
+#include "clang/AST/ParentMapContext.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/CheckerManager.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
+#include "llvm/ADT/STLExtras.h"
+
+using namespace clang;
+using namespace ento;
+using namespace errno_modeling;
+
+namespace {
+
+class ErrnoChecker
+    : public Checker<check::Location, check::PreCall, check::RegionChanges> {
+public:
+  void checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
+                     CheckerContext &) const;
+  void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
+  ProgramStateRef
+  checkRegionChanges(ProgramStateRef State,
+                     const InvalidatedSymbols *Invalidated,
+                     ArrayRef<const MemRegion *> ExplicitRegions,
+                     ArrayRef<const MemRegion *> Regions,
+                     const LocationContext *LCtx, const CallEvent *Call) const;
+
+private:
+  void generateErrnoNotCheckedBug(CheckerContext &C, ProgramStateRef State,
+                                  const MemRegion *ErrnoRegion,
+                                  const CallEvent *CallMayChangeErrno) const;
+
+  BugType BT_InvalidErrnoRead{this, "Value of 'errno' could be undefined",
+                              "Error handling"};
+  BugType BT_ErrnoNotChecked{this, "Value of 'errno' was not checked",
+                             "Error handling"};
+};
+
+} // namespace
+
+static ProgramStateRef setErrnoStateIrrelevant(ProgramStateRef State) {
+  return setErrnoState(State, Errno_Irrelevant);
+}
+
+void ErrnoChecker::generateErrnoNotCheckedBug(
+    CheckerContext &C, ProgramStateRef State, const MemRegion *ErrnoRegion,
+    const CallEvent *CallMayChangeErrno) const {
+  if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) {
+    SmallString<100> StrBuf;
+    llvm::raw_svector_ostream OS(StrBuf);
+    if (CallMayChangeErrno) {
+      OS << "Value of 'errno' was not checked and may be overwritten by "
+            "function '";
+      const auto *CallD =
+          dyn_cast_or_null<FunctionDecl>(CallMayChangeErrno->getDecl());
+      assert(CallD && CallD->getIdentifier());
+      OS << CallD->getIdentifier()->getName() << "'";
+    } else {
+      OS << "Value of 'errno' was not checked and is overwritten here";
+    }
+    auto BR = std::make_unique<PathSensitiveBugReport>(BT_ErrnoNotChecked,
+                                                       OS.str(), N);
+    BR->markInteresting(ErrnoRegion);
+    C.emitReport(std::move(BR));
+  }
+}
+
+void ErrnoChecker::checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
+                                 CheckerContext &C) const {
+  Optional<ento::Loc> ErrnoLoc = getErrnoLoc(C.getState());
+  if (!ErrnoLoc)
+    return;
+
+  auto L = Loc.getAs<ento::Loc>();
+  if (!L || *ErrnoLoc != *L)
+    return;
+
+  ProgramStateRef State = C.getState();
+  ErrnoCheckState EState = getErrnoState(State);
+
+  if (IsLoad) {
+    switch (EState) {
+    case Errno_MustNotBeChecked:
+      // Read of 'errno' when it may have undefined value.
+      if (ExplodedNode *N = C.generateErrorNode()) {
+        auto BR = std::make_unique<PathSensitiveBugReport>(
+            BT_InvalidErrnoRead, "An undefined value may be read from 'errno'",
+            N);
+        BR->markInteresting(ErrnoLoc->getAsRegion());
+        C.emitReport(std::move(BR));
+      }
+      break;
+    case Errno_MustBeChecked:
+      // 'errno' has to be checked. A load is required for this, with no more
+      // information we can assume that it is checked somehow.
+      // After this place 'errno' is allowed to be read and written.
+      State = setErrnoStateIrrelevant(State);
+      C.addTransition(State);
+      break;
+    default:
+      break;
+    }
+  } else {
+    switch (EState) {
+    case Errno_MustBeChecked:
+      // 'errno' is overwritten without a read before but it should have been
+      // checked.
+      generateErrnoNotCheckedBug(C, setErrnoStateIrrelevant(State),
+                                 ErrnoLoc->getAsRegion(), nullptr);
+      break;
+    case Errno_MustNotBeChecked:
+      // Write to 'errno' when it is not allowed to be read.
+      // After this place 'errno' is allowed to be read and written.
+      State = setErrnoStateIrrelevant(State);
+      C.addTransition(State);
+      break;
+    default:
+      break;
+    }
+  }
+}
+
+void ErrnoChecker::checkPreCall(const CallEvent &Call,
+                                CheckerContext &C) const {
+  const auto *CallF = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
+  if (!CallF)
+    return;
+
+  CallF = CallF->getCanonicalDecl();
+  // If 'errno' must be checked, it should be done as soon as possible, and
+  // before any other call to a system function (something in a system header).
+  // To avoid use of a long list of functions that may change 'errno'
+  // (which may be different with standard library versions) assume that any
+  // function can change it.
+  // A list of special functions can be used that are allowed here without
+  // generation of diagnostic. For now the only such case is 'errno' itself.
+  // Probably 'strerror'?
+  if (CallF->isExternC() && CallF->isGlobal() &&
+      C.getSourceManager().isInSystemHeader(CallF->getLocation()) &&
+      !isErrno(CallF)) {
+    if (getErrnoState(C.getState()) == Errno_MustBeChecked) {
+      Optional<ento::Loc> ErrnoLoc = getErrnoLoc(C.getState());
+      assert(ErrnoLoc && "ErrnoLoc should exist if an errno state is set.");
+      generateErrnoNotCheckedBug(C, setErrnoStateIrrelevant(C.getState()),
+                                 ErrnoLoc->getAsRegion(), &Call);
+    }
+  }
+}
+
+ProgramStateRef ErrnoChecker::checkRegionChanges(
+    ProgramStateRef State, const InvalidatedSymbols *Invalidated,
+    ArrayRef<const MemRegion *> ExplicitRegions,
+    ArrayRef<const MemRegion *> Regions, const LocationContext *LCtx,
+    const CallEvent *Call) const {
+  Optional<ento::Loc> ErrnoLoc = getErrnoLoc(State);
+  if (!ErrnoLoc)
+    return State;
+  const MemRegion *ErrnoRegion = ErrnoLoc->getAsRegion();
+
+  // If 'errno' is invalidated we can not know if it is checked or written into,
+  // allow read and write without bug reports.
+  if (llvm::is_contained(Regions, ErrnoRegion))
+    return setErrnoStateIrrelevant(State);
+
+  // Always reset errno state when the system memory space is invalidated.
+  // The ErrnoRegion is not always found in the list in this case.
+  if (llvm::is_contained(Regions, ErrnoRegion->getMemorySpace()))
+    return setErrnoStateIrrelevant(State);
+
+  return State;
+}
+
+void ento::registerErrnoChecker(CheckerManager &mgr) {
+  mgr.registerChecker<ErrnoChecker>();
+}
+
+bool ento::shouldRegisterErrnoChecker(const CheckerManager &mgr) {
+  return true;
+}
Index: clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
===================================================================
--- clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -40,6 +40,7 @@
   DynamicTypePropagation.cpp
   DynamicTypeChecker.cpp
   EnumCastOutOfRangeChecker.cpp
+  ErrnoChecker.cpp
   ErrnoModeling.cpp
   ErrnoTesterChecker.cpp
   ExprInspectionChecker.cpp
Index: clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
===================================================================
--- clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -539,6 +539,11 @@
 
 let ParentPackage = UnixAlpha in {
 
+def ErrnoChecker : Checker<"Errno">,
+  HelpText<"Check not recommended uses of 'errno'">,
+  Dependencies<[ErrnoModeling]>,
+  Documentation<HasAlphaDocumentation>;
+
 def ChrootChecker : Checker<"Chroot">,
   HelpText<"Check improper use of chroot">,
   Documentation<HasAlphaDocumentation>;
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to