balazske created this revision.
Herald added subscribers: cfe-commits, martong, Charusso, gamesh411, dkrupp, 
donat.nagy, Szelethus, mikhail.ramalho, a.sidorin, szepet, baloghadamsoftware, 
xazax.hun.
Herald added a reviewer: Szelethus.
Herald added a project: clang.

The validity checks are performed in precall callback.

Semantic of checking for opened stream has changed:
It is not allowed at all to use the stream after 'fclose'.
This is what cppreference.com says at fclose.
So the double-close error changes to use-after-close.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D75163

Files:
  clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp

Index: clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
===================================================================
--- clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
+++ clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
@@ -34,7 +34,7 @@
 
   bool isOpened() const { return K == Opened; }
   bool isClosed() const { return K == Closed; }
-  //bool isOpenFailed() const { return K == OpenFailed; }
+  bool isOpenFailed() const { return K == OpenFailed; }
   //bool isEscaped() const { return K == Escaped; }
 
   bool operator==(const StreamState &X) const { return K == X.K; }
@@ -50,66 +50,77 @@
 };
 
 class StreamChecker;
-
-using FnCheck = std::function<void(const StreamChecker *, const CallEvent &,
+struct FnDescription;
+using FnCheck = std::function<void(const StreamChecker *, const FnDescription *, const CallEvent &,
                                    CheckerContext &)>;
 
 struct FnDescription {
+  FnCheck PreFn;
   FnCheck EvalFn;
+  unsigned int StreamArgI;
 };
 
-class StreamChecker : public Checker<eval::Call,
+class StreamChecker : public Checker<check::PreCall, eval::Call,
                                      check::DeadSymbols > {
   mutable std::unique_ptr<BuiltinBug> BT_nullfp, BT_illegalwhence,
-      BT_doubleclose, BT_ResourceLeak;
+      BT_UseAfterClose, BT_UseAfterOpenFailed, BT_ResourceLeak;
 
 public:
+  void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
   bool evalCall(const CallEvent &Call, CheckerContext &C) const;
   void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
 
 private:
 
   CallDescriptionMap<FnDescription> FnDescriptions = {
-      {{"fopen"}, {&StreamChecker::evalFopen}},
-      {{"freopen", 3}, {&StreamChecker::evalFreopen}},
-      {{"tmpfile"}, {&StreamChecker::evalFopen}},
-      {{"fclose", 1}, {&StreamChecker::evalFclose}},
-      {{"fread", 4},
-       {std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 3)}},
-      {{"fwrite", 4},
-       {std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 3)}},
-      {{"fseek", 3}, {&StreamChecker::evalFseek}},
-      {{"ftell", 1},
-       {std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}},
-      {{"rewind", 1},
-       {std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}},
-      {{"fgetpos", 2},
-       {std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}},
-      {{"fsetpos", 2},
-       {std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}},
-      {{"clearerr", 1},
-       {std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}},
-      {{"feof", 1},
-       {std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}},
-      {{"ferror", 1},
-       {std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}},
-      {{"fileno", 1},
-       {std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}},
+      {{"fopen"}, {{}, &StreamChecker::evalFopen, -1}},
+      {{"freopen", 3}, {&StreamChecker::preFreopen, &StreamChecker::evalFreopen, 2}},
+      {{"tmpfile"}, {{}, &StreamChecker::evalFopen, -1}},
+      {{"fclose", 1}, {&StreamChecker::preDefault, &StreamChecker::evalFclose, 0}},
+      {{"fread", 4}, {&StreamChecker::preDefault, {}, 3}},
+      {{"fwrite", 4}, {&StreamChecker::preDefault, {}, 3}},
+      {{"fseek", 3}, {&StreamChecker::preFseek, {}}},
+      {{"ftell", 1}, {&StreamChecker::preDefault, {}, 0}},
+      {{"rewind", 1}, {&StreamChecker::preDefault, {}, 0}},
+      {{"fgetpos", 2}, {&StreamChecker::preDefault, {}, 0}},
+      {{"fsetpos", 2}, {&StreamChecker::preDefault, {}, 0}},
+      {{"clearerr", 1}, {&StreamChecker::preDefault, {}, 0}},
+      {{"feof", 1}, {&StreamChecker::preDefault, {}, 0}},
+      {{"ferror", 1}, {&StreamChecker::preDefault, {}, 0}},
+      {{"fileno", 1}, {&StreamChecker::preDefault, {}, 0}},
   };
 
-  void evalFopen(const CallEvent &Call, CheckerContext &C) const;
-  void evalFreopen(const CallEvent &Call, CheckerContext &C) const;
-  void evalFclose(const CallEvent &Call, CheckerContext &C) const;
-  void evalFseek(const CallEvent &Call, CheckerContext &C) const;
-  void checkArgNullStream(const CallEvent &Call, CheckerContext &C,
-                          unsigned ArgI) const;
-
-  ProgramStateRef checkNullStream(SVal SV, CheckerContext &C,
-                                  ProgramStateRef State) const;
-  ProgramStateRef checkFseekWhence(SVal SV, CheckerContext &C,
-                                   ProgramStateRef State) const;
-  ProgramStateRef checkDoubleClose(const CallEvent &Call, CheckerContext &C,
-                                   ProgramStateRef State) const;
+  void preDefault(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const;
+  void preFseek(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const;
+  void preFreopen(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const;
+
+  void evalFopen(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const;
+  void evalFreopen(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const;
+  void evalFclose(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const;
+
+  ProgramStateRef ensureStreamNonNull(SVal StreamVal, CheckerContext &C, ProgramStateRef State) const;
+  ProgramStateRef ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C, ProgramStateRef State) const;
+  ProgramStateRef ensureStreamOpened(SVal StreamVal, CheckerContext &C,
+                                                ProgramStateRef State) const;
+
+  const FnDescription *lookupFn(const CallEvent &Call) const {
+    // Recognize "global C functions" with only integral or pointer arguments
+    // (and matching name) as stream functions.
+    if (!Call.isGlobalCFunction())
+      return nullptr;
+    for (auto P : Call.parameters()) {
+      QualType T = P->getType();
+      if (!T->isIntegralOrEnumerationType() && !T->isPointerType())
+        return nullptr;
+    }
+    // Ensure that not a member function is called.
+    const auto *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
+    if (!FD || FD->getKind() != Decl::Function)
+      return nullptr;
+
+    return FnDescriptions.lookup(Call);
+  }
+
 };
 
 } // end anonymous namespace
@@ -117,31 +128,64 @@
 REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
 
 
-bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
-  const auto *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
-  if (!FD || FD->getKind() != Decl::Function)
-    return false;
-
-  // Recognize "global C functions" with only integral or pointer arguments
-  // (and matching name) as stream functions.
-  if (!Call.isGlobalCFunction())
-    return false;
-  for (auto P : Call.parameters()) {
-    QualType T = P->getType();
-    if (!T->isIntegralOrEnumerationType() && !T->isPointerType())
-      return false;
-  }
+void StreamChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const {
+  const FnDescription *Desc = lookupFn(Call);
+  if (!Desc)
+    return;
 
-  const FnDescription *Description = FnDescriptions.lookup(Call);
-  if (!Description)
-    return false;
+  (Desc->PreFn)(this, Desc, Call, C);
+}
+
+bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
+  const FnDescription *Desc = lookupFn(Call);
+  if (!Desc)
+    return;
 
-  (Description->EvalFn)(this, Call, C);
+  (Desc->EvalFn)(this, Desc, Call, C);
 
   return C.isDifferent();
 }
 
-void StreamChecker::evalFopen(const CallEvent &Call, CheckerContext &C) const {
+
+void StreamChecker::preDefault(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const {
+  ProgramStateRef State = C.getState();
+  State = ensureStreamNonNull(Call.getArgSVal(Desc->StreamArgI), C, State);
+  if (!State)
+    return;
+  State = ensureStreamOpened(Call.getArgSVal(Desc->StreamArgI), C, State);
+  if (!State)
+    return;
+
+  C.addTransition(State);
+}
+
+void StreamChecker::preFseek(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const {
+  ProgramStateRef State = C.getState();
+  State = ensureStreamNonNull(Call.getArgSVal(Desc->StreamArgI), C, State);
+  if (!State)
+    return;
+  State = ensureStreamOpened(Call.getArgSVal(Desc->StreamArgI), C, State);
+  if (!State)
+    return;
+  State = ensureFseekWhenceCorrect(Call.getArgSVal(2), C, State);
+  if (!State)
+    return;
+  
+  C.addTransition(State);
+}
+
+void StreamChecker::preFreopen(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const {
+  ProgramStateRef State = C.getState();
+  // Do not allow NULL as passed stream pointer.
+  // This is not specified in the man page but may crash on some system.
+  State = ensureStreamNonNull(Call.getArgSVal(Desc->StreamArgI), C, State);
+  if (!State)
+    return;
+  
+  C.addTransition(State);
+}
+
+void StreamChecker::evalFopen(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const {
   ProgramStateRef State = C.getState();
   SValBuilder &SVB = C.getSValBuilder();
   const LocationContext *LCtx = C.getPredecessor()->getLocationContext();
@@ -170,7 +214,7 @@
   C.addTransition(StateNull);
 }
 
-void StreamChecker::evalFreopen(const CallEvent &Call,
+void StreamChecker::evalFreopen(const FnDescription *Desc, const CallEvent &Call,
                                 CheckerContext &C) const {
   ProgramStateRef State = C.getState();
 
@@ -181,14 +225,9 @@
   Optional<DefinedSVal> StreamVal = Call.getArgSVal(2).getAs<DefinedSVal>();
   if (!StreamVal)
     return;
-  // Do not allow NULL as passed stream pointer.
-  // This is not specified in the man page but may crash on some system.
-  State = checkNullStream(*StreamVal, C, State);
-  if (!State)
-    return;
 
   SymbolRef StreamSym = StreamVal->getAsSymbol();
-  // Do not care about special values for stream ("(FILE *)0x12345"?).
+  // Do not care about concrete values for stream ("(FILE *)0x12345"?).
   if (!StreamSym)
     return;
 
@@ -212,49 +251,34 @@
   C.addTransition(StateRetNull);
 }
 
-void StreamChecker::evalFclose(const CallEvent &Call, CheckerContext &C) const {
+void StreamChecker::evalFclose(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const {
   ProgramStateRef State = C.getState();
-  State = checkDoubleClose(Call, C, State);
-  if (State)
-    C.addTransition(State);
-}
-
-void StreamChecker::evalFseek(const CallEvent &Call, CheckerContext &C) const {
-  const Expr *AE2 = Call.getArgExpr(2);
-  if (!AE2)
+  SymbolRef Sym = Call.getArgSVal(Desc->StreamArgI).getAsSymbol();
+  if (!Sym)
     return;
 
-  ProgramStateRef State = C.getState();
-
-  State = checkNullStream(Call.getArgSVal(0), C, State);
-  if (!State)
+  const StreamState *SS = State->get<StreamMap>(Sym);
+  if (!SS)
     return;
 
-  State =
-      checkFseekWhence(State->getSVal(AE2, C.getLocationContext()), C, State);
-  if (!State)
-    return;
+  // Close the File Descriptor.
+  // Regardless if the close fails or not, stream becomes "closed"
+  // and can not be used any more.
+  State = State->set<StreamMap>(Sym, StreamState::getClosed());
 
   C.addTransition(State);
 }
 
-void StreamChecker::checkArgNullStream(const CallEvent &Call, CheckerContext &C,
-                                       unsigned ArgI) const {
-  ProgramStateRef State = C.getState();
-  State = checkNullStream(Call.getArgSVal(ArgI), C, State);
-  if (State)
-    C.addTransition(State);
-}
 
-ProgramStateRef StreamChecker::checkNullStream(SVal SV, CheckerContext &C,
-                                               ProgramStateRef State) const {
-  Optional<DefinedSVal> DV = SV.getAs<DefinedSVal>();
-  if (!DV)
+ProgramStateRef StreamChecker::ensureStreamNonNull(SVal StreamVal, CheckerContext &C, ProgramStateRef State) const {
+  auto Stream = StreamVal.getAs<DefinedSVal>();
+  if (!Stream)
     return State;
 
   ConstraintManager &CM = C.getConstraintManager();
+
   ProgramStateRef StateNotNull, StateNull;
-  std::tie(StateNotNull, StateNull) = CM.assumeDual(C.getState(), *DV);
+  std::tie(StateNotNull, StateNull) = CM.assumeDual(C.getState(), *Stream);
 
   if (!StateNotNull && StateNull) {
     if (ExplodedNode *N = C.generateErrorNode(StateNull)) {
@@ -271,9 +295,8 @@
 }
 
 // Check the legality of the 'whence' argument of 'fseek'.
-ProgramStateRef StreamChecker::checkFseekWhence(SVal SV, CheckerContext &C,
-                                                ProgramStateRef State) const {
-  Optional<nonloc::ConcreteInt> CI = SV.getAs<nonloc::ConcreteInt>();
+ProgramStateRef StreamChecker::ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C, ProgramStateRef State) const {
+  Optional<nonloc::ConcreteInt> CI = WhenceVal.getAs<nonloc::ConcreteInt>();
   if (!CI)
     return State;
 
@@ -295,38 +318,52 @@
   return State;
 }
 
-ProgramStateRef StreamChecker::checkDoubleClose(const CallEvent &Call,
+// Check:
+// Using a stream pointer after 'fclose' causes undefined behavior
+// according to cppreference.com .
+// Using a stream that has failed to open is likely to cause problems, but not
+// explicitly mentioned in documentation.
+ProgramStateRef StreamChecker::ensureStreamOpened(SVal StreamVal,
                                                 CheckerContext &C,
                                                 ProgramStateRef State) const {
-  SymbolRef Sym = Call.getArgSVal(0).getAsSymbol();
+  SymbolRef Sym = StreamVal.getAsSymbol();
   if (!Sym)
-    return State;
+    return nullptr;
 
   const StreamState *SS = State->get<StreamMap>(Sym);
-
-  // If the file stream is not tracked, return.
   if (!SS)
-    return State;
+    return;
 
-  // Check: Double close a File Descriptor could cause undefined behaviour.
-  // Conforming to man-pages
   if (SS->isClosed()) {
     ExplodedNode *N = C.generateErrorNode();
     if (N) {
-      if (!BT_doubleclose)
-        BT_doubleclose.reset(new BuiltinBug(
-            this, "Double fclose", "Try to close a file Descriptor already"
-                                   " closed. Cause undefined behaviour."));
+      if (!BT_UseAfterClose)
+        BT_UseAfterClose.reset(new BuiltinBug(
+            this, "Stream used after close", "File descriptor is used after it was closed."
+                                   "Cause undefined behaviour."));
       C.emitReport(std::make_unique<PathSensitiveBugReport>(
-          *BT_doubleclose, BT_doubleclose->getDescription(), N));
+          *BT_UseAfterClose, BT_UseAfterClose->getDescription(), N));
       return nullptr;
     }
-
     return State;
   }
 
-  // Close the File Descriptor.
-  State = State->set<StreamMap>(Sym, StreamState::getClosed());
+  if (SS->isOpenFailed()) {
+    // This should usually not occur because stream pointer is NULL.
+    // But freopen can cause a state when stream pointer remains non-null but failed to open.
+    ExplodedNode *N = C.generateErrorNode();
+    if (N) {
+      if (!BT_UseAfterOpenFailed)
+        BT_UseAfterOpenFailed.reset(new BuiltinBug(
+            this, "Stream used after failed open", "(Re-)Opening the file descriptor has failed."
+                                   "Using it can cause undefined behaviour."));
+      C.emitReport(std::make_unique<PathSensitiveBugReport>(
+          *BT_UseAfterOpenFailed, BT_UseAfterOpenFailed->getDescription(), N));
+      return nullptr;
+    }
+
+    return State;
+  }
 
   return State;
 }
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to