Hi,

I wrote a patch for Clang Static Analyzer's StreamChecker. This patch:
1. adds a check for descriptor access (read/write) after it being closed
2. adds support of directory operations (opendir/closedir and some other)
3. fixes issue in double close check: descriptor that was not tracked before was not marked as closed while calling a close function
4. adds fprintf and fscanf functions to track.

Is it OK to commit or have I done something wrong?

--
Best regards,
Alexey Sidorin,
Programming Tools Part, CSG, SMRC, Samsung Electronics

Index: StreamChecker.cpp
===================================================================
--- StreamChecker.cpp	(revision 182465)
+++ StreamChecker.cpp	(working copy)
@@ -56,20 +56,27 @@
   }
 };
 
+// TODO: add open/close functions to track.
 class StreamChecker : public Checker<eval::Call,
                                      check::DeadSymbols > {
   mutable IdentifierInfo *II_fopen, *II_tmpfile, *II_fclose, *II_fread,
-                 *II_fwrite, 
-                 *II_fseek, *II_ftell, *II_rewind, *II_fgetpos, *II_fsetpos,  
-                 *II_clearerr, *II_feof, *II_ferror, *II_fileno;
+                 *II_fwrite, *II_fscanf, *II_fprintf, *II_fseek, *II_ftell, 
+                 *II_rewind, *II_fgetpos, *II_fsetpos, *II_clearerr, *II_feof,
+                 *II_ferror, *II_fileno, *II_opendir, *II_closedir,
+                 *II_fdopendir, *II_scandir, *II_seekdir, *II_readdir,
+                 *II_dirfd, *II_rewinddir, *II_telldir;
   mutable OwningPtr<BuiltinBug> BT_nullfp, BT_illegalwhence,
-                                      BT_doubleclose, BT_ResourceLeak;
+                                      BT_doubleclose, BT_ResourceLeak,
+                                      BT_accessAfterClose;
 
 public:
   StreamChecker() 
-    : II_fopen(0), II_tmpfile(0) ,II_fclose(0), II_fread(0), II_fwrite(0), 
-      II_fseek(0), II_ftell(0), II_rewind(0), II_fgetpos(0), II_fsetpos(0), 
-      II_clearerr(0), II_feof(0), II_ferror(0), II_fileno(0) {}
+    : II_fopen(0), II_tmpfile(0) ,II_fclose(0), II_fread(0), II_fwrite(0),
+      II_fscanf(0), II_fprintf(0), II_fseek(0), II_ftell(0), II_rewind(0),
+      II_fgetpos(0), II_fsetpos(0), II_clearerr(0), II_feof(0),
+      II_ferror(0), II_fileno(0), II_opendir(0), II_closedir(0),
+      II_fdopendir(0), II_scandir(0), II_seekdir(0), II_readdir(0),
+      II_dirfd(0), II_rewinddir(0), II_telldir(0) {}
 
   bool evalCall(const CallExpr *CE, CheckerContext &C) const;
   void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
@@ -77,12 +84,23 @@
 private:
   void Fopen(CheckerContext &C, const CallExpr *CE) const;
   void Tmpfile(CheckerContext &C, const CallExpr *CE) const;
+  void Opendir(CheckerContext &C, const CallExpr *CE) const;
+  void Fdopendir(CheckerContext &C, const CallExpr *CE) const;
   void Fclose(CheckerContext &C, const CallExpr *CE) const;
+  void Closedir(CheckerContext &C, const CallExpr *CE) const;
   void Fread(CheckerContext &C, const CallExpr *CE) const;
+  void Dirfd(CheckerContext &C, const CallExpr *CE) const;
+  void Scandir(CheckerContext &C, const CallExpr *CE) const;
+  void Fscanf(CheckerContext &C, const CallExpr *CE) const;
+  void Readdir(CheckerContext &C, const CallExpr *CE) const;
   void Fwrite(CheckerContext &C, const CallExpr *CE) const;
+  void Fprintf(CheckerContext &C, const CallExpr *CE) const;
   void Fseek(CheckerContext &C, const CallExpr *CE) const;
+  void Seekdir(CheckerContext &C, const CallExpr *CE) const;
   void Ftell(CheckerContext &C, const CallExpr *CE) const;
+  void Telldir(CheckerContext &C, const CallExpr *CE) const;
   void Rewind(CheckerContext &C, const CallExpr *CE) const;
+  void Rewinddir(CheckerContext &C, const CallExpr *CE) const;
   void Fgetpos(CheckerContext &C, const CallExpr *CE) const;
   void Fsetpos(CheckerContext &C, const CallExpr *CE) const;
   void Clearerr(CheckerContext &C, const CallExpr *CE) const;
@@ -91,11 +109,14 @@
   void Fileno(CheckerContext &C, const CallExpr *CE) const;
 
   void OpenFileAux(CheckerContext &C, const CallExpr *CE) const;
-  
+  void AccessAux(CheckerContext &C, const CallExpr *CE, int Argno) const;
+
   ProgramStateRef CheckNullStream(SVal SV, ProgramStateRef state, 
                                  CheckerContext &C) const;
   ProgramStateRef CheckDoubleClose(const CallExpr *CE, ProgramStateRef state, 
                                  CheckerContext &C) const;
+  bool CheckAlreadyClosed(SVal SV, ProgramStateRef state, 
+                                 CheckerContext &C) const;
 };
 
 } // end anonymous namespace
@@ -111,20 +132,42 @@
   ASTContext &Ctx = C.getASTContext();
   if (!II_fopen)
     II_fopen = &Ctx.Idents.get("fopen");
+  if (!II_opendir)
+    II_opendir = &Ctx.Idents.get("opendir");
+  if (!II_fdopendir)
+    II_fdopendir = &Ctx.Idents.get("fdopendir");
   if (!II_tmpfile)
     II_tmpfile = &Ctx.Idents.get("tmpfile");
   if (!II_fclose)
     II_fclose = &Ctx.Idents.get("fclose");
+  if (!II_closedir)
+    II_closedir = &Ctx.Idents.get("closedir");
   if (!II_fread)
     II_fread = &Ctx.Idents.get("fread");
+  if (!II_readdir)
+    II_readdir = &Ctx.Idents.get("readdir");
+  if (!II_scandir)
+    II_scandir = &Ctx.Idents.get("scandir");
+  if (!II_dirfd)
+    II_dirfd = &Ctx.Idents.get("dirfd");
   if (!II_fwrite)
     II_fwrite = &Ctx.Idents.get("fwrite");
+  if (!II_fscanf)
+      II_fscanf = &Ctx.Idents.get("fscanf");
+  if (!II_fprintf)
+      II_fprintf = &Ctx.Idents.get("fprintf");
   if (!II_fseek)
     II_fseek = &Ctx.Idents.get("fseek");
+  if (!II_seekdir)
+    II_seekdir = &Ctx.Idents.get("seekdir");
   if (!II_ftell)
     II_ftell = &Ctx.Idents.get("ftell");
+  if (!II_telldir)
+    II_telldir = &Ctx.Idents.get("telldir");
   if (!II_rewind)
     II_rewind = &Ctx.Idents.get("rewind");
+  if (!II_rewinddir)
+    II_rewinddir = &Ctx.Idents.get("rewinddir");
   if (!II_fgetpos)
     II_fgetpos = &Ctx.Idents.get("fgetpos");
   if (!II_fsetpos)
@@ -142,6 +185,14 @@
     Fopen(C, CE);
     return true;
   }
+  if (FD->getIdentifier() == II_fdopendir) {
+    Fdopendir(C, CE);
+    return true;
+  }
+  if (FD->getIdentifier() == II_opendir) {
+    Opendir(C, CE);
+    return true;
+  }
   if (FD->getIdentifier() == II_tmpfile) {
     Tmpfile(C, CE);
     return true;
@@ -150,26 +201,62 @@
     Fclose(C, CE);
     return true;
   }
+  if (FD->getIdentifier() == II_closedir) {
+    Closedir(C, CE);
+    return true;
+  }
   if (FD->getIdentifier() == II_fread) {
     Fread(C, CE);
     return true;
   }
+  if (FD->getIdentifier() == II_fscanf) {
+    Fscanf(C, CE);
+    return true;
+  }
+  if (FD->getIdentifier() == II_readdir) {
+    Readdir(C, CE);
+    return true;
+  }
+  if (FD->getIdentifier() == II_dirfd) {
+    Dirfd(C, CE);
+    return true;
+  }
+  if (FD->getIdentifier() == II_scandir) {
+    Scandir(C, CE);
+    return true;
+  }
   if (FD->getIdentifier() == II_fwrite) {
     Fwrite(C, CE);
     return true;
   }
+  if (FD->getIdentifier() == II_fprintf) {
+    Fprintf(C, CE);
+    return true;
+  }
   if (FD->getIdentifier() == II_fseek) {
     Fseek(C, CE);
     return true;
   }
+  if (FD->getIdentifier() == II_seekdir) {
+    Seekdir(C, CE);
+    return true;
+  }
   if (FD->getIdentifier() == II_ftell) {
     Ftell(C, CE);
     return true;
   }
+  if (FD->getIdentifier() == II_telldir) {
+    Telldir(C, CE);
+    return true;
+  }
   if (FD->getIdentifier() == II_rewind) {
     Rewind(C, CE);
     return true;
   }
+  if (FD->getIdentifier() == II_rewinddir) {
+    Rewinddir(C, CE);
+    return true;
+  }
   if (FD->getIdentifier() == II_fgetpos) {
     Fgetpos(C, CE);
     return true;
@@ -202,6 +289,14 @@
   OpenFileAux(C, CE);
 }
 
+void StreamChecker::Fdopendir(CheckerContext &C, const CallExpr *CE) const {
+  OpenFileAux(C, CE);
+}
+
+void StreamChecker::Opendir(CheckerContext &C, const CallExpr *CE) const {
+  OpenFileAux(C, CE);
+}
+
 void StreamChecker::Tmpfile(CheckerContext &C, const CallExpr *CE) const {
   OpenFileAux(C, CE);
 }
@@ -232,31 +327,64 @@
   }
 }
 
+void StreamChecker::AccessAux(CheckerContext &C, const CallExpr *CE, 
+                              int Argno) const {
+  ProgramStateRef state = C.getState();
+  SVal SV = state->getSVal(CE->getArg(Argno), C.getLocationContext());
+  if (!CheckNullStream(SV, state, C))
+    return;
+  CheckAlreadyClosed(SV, state, C);
+}
+
 void StreamChecker::Fclose(CheckerContext &C, const CallExpr *CE) const {
   ProgramStateRef state = CheckDoubleClose(CE, C.getState(), C);
   if (state)
     C.addTransition(state);
 }
 
+void StreamChecker::Closedir(CheckerContext &C, const CallExpr *CE) const {
+  ProgramStateRef state = CheckDoubleClose(CE, C.getState(), C);
+  if (state)
+    C.addTransition(state);
+}
+
 void StreamChecker::Fread(CheckerContext &C, const CallExpr *CE) const {
-  ProgramStateRef state = C.getState();
-  if (!CheckNullStream(state->getSVal(CE->getArg(3), C.getLocationContext()),
-                       state, C))
-    return;
+  AccessAux(C, CE, 3);
 }
 
 void StreamChecker::Fwrite(CheckerContext &C, const CallExpr *CE) const {
-  ProgramStateRef state = C.getState();
-  if (!CheckNullStream(state->getSVal(CE->getArg(3), C.getLocationContext()),
-                       state, C))
-    return;
+  AccessAux(C, CE, 3);
 }
 
+void StreamChecker::Fscanf(CheckerContext &C, const CallExpr *CE) const {
+  AccessAux(C, CE, 0);
+}
+
+void StreamChecker::Fprintf(CheckerContext &C, const CallExpr *CE) const {
+  AccessAux(C, CE, 0);
+}
+
+void StreamChecker::Scandir(CheckerContext &C, const CallExpr *CE) const {
+  AccessAux(C, CE, 0);
+}
+
+void StreamChecker::Readdir(CheckerContext &C, const CallExpr *CE) const {
+  AccessAux(C, CE, 0);
+}
+
+void StreamChecker::Dirfd(CheckerContext &C, const CallExpr *CE) const {
+  AccessAux(C, CE, 0);
+}
+
 void StreamChecker::Fseek(CheckerContext &C, const CallExpr *CE) const {
   ProgramStateRef state = C.getState();
-  if (!(state = CheckNullStream(state->getSVal(CE->getArg(0),
-                                               C.getLocationContext()), state, C)))
+  SVal SV = state->getSVal(CE->getArg(0), C.getLocationContext());
+  if (!(state = CheckNullStream(SV, state, C)))
     return;
+
+  if (CheckAlreadyClosed(SV, state, C))
+    return;
+
   // Check the legality of the 'whence' argument of 'fseek'.
   SVal Whence = state->getSVal(CE->getArg(2), C.getLocationContext());
   Optional<nonloc::ConcreteInt> CI = Whence.getAs<nonloc::ConcreteInt>();
@@ -279,60 +407,48 @@
   }
 }
 
+void StreamChecker::Seekdir(CheckerContext &C, const CallExpr *CE) const {
+  AccessAux(C, CE, 0);
+}
+
 void StreamChecker::Ftell(CheckerContext &C, const CallExpr *CE) const {
-  ProgramStateRef state = C.getState();
-  if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()),
-                       state, C))
-    return;
+  AccessAux(C, CE, 0);
 }
 
+void StreamChecker::Telldir(CheckerContext &C, const CallExpr *CE) const {
+  AccessAux(C, CE, 0);
+}
+
 void StreamChecker::Rewind(CheckerContext &C, const CallExpr *CE) const {
-  ProgramStateRef state = C.getState();
-  if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()),
-                       state, C))
-    return;
+  AccessAux(C, CE, 0);
 }
 
+void StreamChecker::Rewinddir(CheckerContext &C, const CallExpr *CE) const {
+  AccessAux(C, CE, 0);
+}
+
 void StreamChecker::Fgetpos(CheckerContext &C, const CallExpr *CE) const {
-  ProgramStateRef state = C.getState();
-  if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()),
-                       state, C))
-    return;
+  AccessAux(C, CE, 0);
 }
 
 void StreamChecker::Fsetpos(CheckerContext &C, const CallExpr *CE) const {
-  ProgramStateRef state = C.getState();
-  if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()),
-                       state, C))
-    return;
+  AccessAux(C, CE, 0);
 }
 
 void StreamChecker::Clearerr(CheckerContext &C, const CallExpr *CE) const {
-  ProgramStateRef state = C.getState();
-  if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()),
-                       state, C))
-    return;
+  AccessAux(C, CE, 0);
 }
 
 void StreamChecker::Feof(CheckerContext &C, const CallExpr *CE) const {
-  ProgramStateRef state = C.getState();
-  if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()),
-                       state, C))
-    return;
+  AccessAux(C, CE, 0);
 }
 
 void StreamChecker::Ferror(CheckerContext &C, const CallExpr *CE) const {
-  ProgramStateRef state = C.getState();
-  if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()),
-                       state, C))
-    return;
+  AccessAux(C, CE, 0);
 }
 
 void StreamChecker::Fileno(CheckerContext &C, const CallExpr *CE) const {
-  ProgramStateRef state = C.getState();
-  if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()),
-                       state, C))
-    return;
+  AccessAux(C, CE, 0);
 }
 
 ProgramStateRef StreamChecker::CheckNullStream(SVal SV, ProgramStateRef state,
@@ -365,21 +481,17 @@
     state->getSVal(CE->getArg(0), C.getLocationContext()).getAsSymbol();
   if (!Sym)
     return state;
-  
+ 
   const StreamState *SS = state->get<StreamMap>(Sym);
 
-  // If the file stream is not tracked, return.
-  if (!SS)
-    return state;
-  
   // Check: Double close a File Descriptor could cause undefined behaviour.
   // Conforming to man-pages
-  if (SS->isClosed()) {
+  if (SS && SS->isClosed()) {
     ExplodedNode *N = C.generateSink();
     if (N) {
       if (!BT_doubleclose)
-        BT_doubleclose.reset(new BuiltinBug("Double fclose",
-                                        "Try to close a file Descriptor already"
+        BT_doubleclose.reset(new BuiltinBug("Double close",
+                                        "Try to close a descriptor already"
                                         " closed. Cause undefined behaviour."));
       BugReport *R = new BugReport(*BT_doubleclose,
                                    BT_doubleclose->getDescription(), N);
@@ -389,9 +501,40 @@
   }
   
   // Close the File Descriptor.
+  // If it was not tracked before but gets closed we think it is closed too.
   return state->set<StreamMap>(Sym, StreamState::getClosed(CE));
 }
 
+bool StreamChecker::CheckAlreadyClosed(SVal SV, ProgramStateRef state,
+                                              CheckerContext &C) const {
+  SymbolRef Sym = SV.getAsSymbol();
+  if (!Sym)
+    return false;
+
+  const StreamState *SS = state->get<StreamMap>(Sym);
+
+  // If the file stream is not tracked, return.
+  if (!SS)
+    return false;
+
+  // Check: Access a File Descriptor after it being closed could cause 
+  // undefined behaviour.
+  if (SS->isClosed()) {
+    ExplodedNode *N = C.generateSink();
+    if (N) {
+      if (!BT_accessAfterClose)
+        BT_accessAfterClose.reset(new BuiltinBug("Access after closing descriptor",
+                                        "Try to access a descriptor already"
+                                        " closed. Causes undefined behaviour."));
+      BugReport *R = new BugReport(*BT_accessAfterClose,
+                                   BT_accessAfterClose->getDescription(), N);
+      C.emitReport(R);
+    }
+    return true;
+  }
+  return false;
+}
+
 void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
                                      CheckerContext &C) const {
   // TODO: Clean up the state.
@@ -408,7 +551,7 @@
       if (N) {
         if (!BT_ResourceLeak)
           BT_ResourceLeak.reset(new BuiltinBug("Resource Leak", 
-                         "Opened File never closed. Potential Resource leak."));
+                         "Opened descriptor never closed. Potential Resource leak."));
         BugReport *R = new BugReport(*BT_ResourceLeak, 
                                      BT_ResourceLeak->getDescription(), N);
         C.emitReport(R);
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits

Reply via email to