r180069 - [analyzer] Model strsep(), particularly that it returns its input.

Jordan Rose jordan_rose at apple.com
Mon Apr 22 16:18:42 PDT 2013


Author: jrose
Date: Mon Apr 22 18:18:42 2013
New Revision: 180069

URL: http://llvm.org/viewvc/llvm-project?rev=180069&view=rev
Log:
[analyzer] Model strsep(), particularly that it returns its input.

This handles the false positive leak warning in PR15374, and also serves
as a basic model for the strsep() function.

Modified:
    cfe/trunk/lib/StaticAnalyzer/Checkers/CStringChecker.cpp
    cfe/trunk/test/Analysis/string.c

Modified: cfe/trunk/lib/StaticAnalyzer/Checkers/CStringChecker.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/StaticAnalyzer/Checkers/CStringChecker.cpp?rev=180069&r1=180068&r2=180069&view=diff
==============================================================================
--- cfe/trunk/lib/StaticAnalyzer/Checkers/CStringChecker.cpp (original)
+++ cfe/trunk/lib/StaticAnalyzer/Checkers/CStringChecker.cpp Mon Apr 22 18:18:42 2013
@@ -114,6 +114,8 @@ public:
                         bool isBounded = false,
                         bool ignoreCase = false) const;
 
+  void evalStrsep(CheckerContext &C, const CallExpr *CE) const;
+
   // Utility methods
   std::pair<ProgramStateRef , ProgramStateRef >
   static assumeZero(CheckerContext &C,
@@ -1752,6 +1754,63 @@ void CStringChecker::evalStrcmpCommon(Ch
   C.addTransition(state);
 }
 
+void CStringChecker::evalStrsep(CheckerContext &C, const CallExpr *CE) const {
+  //char *strsep(char **stringp, const char *delim);
+  if (CE->getNumArgs() < 2)
+    return;
+
+  // Sanity: does the search string parameter match the return type?
+  const Expr *SearchStrPtr = CE->getArg(0);
+  QualType CharPtrTy = SearchStrPtr->getType()->getPointeeType();
+  if (CharPtrTy.isNull() ||
+      CE->getType().getUnqualifiedType() != CharPtrTy.getUnqualifiedType())
+    return;
+
+  CurrentFunctionDescription = "strsep()";
+  ProgramStateRef State = C.getState();
+  const LocationContext *LCtx = C.getLocationContext();
+
+  // Check that the search string pointer is non-null (though it may point to
+  // a null string).
+  SVal SearchStrVal = State->getSVal(SearchStrPtr, LCtx);
+  State = checkNonNull(C, State, SearchStrPtr, SearchStrVal);
+  if (!State)
+    return;
+
+  // Check that the delimiter string is non-null.
+  const Expr *DelimStr = CE->getArg(1);
+  SVal DelimStrVal = State->getSVal(DelimStr, LCtx);
+  State = checkNonNull(C, State, DelimStr, DelimStrVal);
+  if (!State)
+    return;
+
+  SValBuilder &SVB = C.getSValBuilder();
+  SVal Result;
+  if (Optional<Loc> SearchStrLoc = SearchStrVal.getAs<Loc>()) {
+    // Get the current value of the search string pointer, as a char*.
+    Result = State->getSVal(*SearchStrLoc, CharPtrTy);
+
+    // Invalidate the search string, representing the change of one delimiter
+    // character to NUL.
+    State = InvalidateBuffer(C, State, SearchStrPtr, Result);
+
+    // Overwrite the search string pointer. The new value is either an address
+    // further along in the same string, or NULL if there are no more tokens.
+    State = State->bindLoc(*SearchStrLoc,
+                           SVB.conjureSymbolVal(getTag(), CE, LCtx, CharPtrTy,
+                                                C.blockCount()));
+  } else {
+    assert(SearchStrVal.isUnknown());
+    // Conjure a symbolic value. It's the best we can do.
+    Result = SVB.conjureSymbolVal(0, CE, LCtx, C.blockCount());
+  }
+
+  // Set the return value, and finish.
+  State = State->BindExpr(CE, LCtx, Result);
+  C.addTransition(State);
+}
+
+
 //===----------------------------------------------------------------------===//
 // The driver method, and other Checker callbacks.
 //===----------------------------------------------------------------------===//
@@ -1762,6 +1821,7 @@ bool CStringChecker::evalCall(const Call
   if (!FDecl)
     return false;
 
+  // FIXME: Poorly-factored string switches are slow.
   FnCheck evalFunction = 0;
   if (C.isCLibraryFunction(FDecl, "memcpy"))
     evalFunction =  &CStringChecker::evalMemcpy;
@@ -1793,6 +1853,8 @@ bool CStringChecker::evalCall(const Call
     evalFunction =  &CStringChecker::evalStrcasecmp;
   else if (C.isCLibraryFunction(FDecl, "strncasecmp"))
     evalFunction =  &CStringChecker::evalStrncasecmp;
+  else if (C.isCLibraryFunction(FDecl, "strsep"))
+    evalFunction =  &CStringChecker::evalStrsep;
   else if (C.isCLibraryFunction(FDecl, "bcopy"))
     evalFunction =  &CStringChecker::evalBcopy;
   else if (C.isCLibraryFunction(FDecl, "bcmp"))

Modified: cfe/trunk/test/Analysis/string.c
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/string.c?rev=180069&r1=180068&r2=180069&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/string.c (original)
+++ cfe/trunk/test/Analysis/string.c Mon Apr 22 18:18:42 2013
@@ -1028,6 +1028,57 @@ void strncasecmp_embedded_null () {
 }
 
 //===----------------------------------------------------------------------===
+// strsep()
+//===----------------------------------------------------------------------===
+
+char *strsep(char **stringp, const char *delim);
+
+void strsep_null_delim(char *s) {
+  strsep(&s, NULL); // expected-warning{{Null pointer argument in call to strsep()}}
+}
+
+void strsep_null_search() {
+  strsep(NULL, ""); // expected-warning{{Null pointer argument in call to strsep()}}
+}
+
+void strsep_return_original_pointer(char *s) {
+  char *original = s;
+  char *result = strsep(&s, ""); // no-warning
+  clang_analyzer_eval(original == result); // expected-warning{{TRUE}}
+}
+
+void strsep_null_string() {
+  char *s = NULL;
+  char *result = strsep(&s, ""); // no-warning
+  clang_analyzer_eval(result == NULL); // expected-warning{{TRUE}}
+}
+
+void strsep_changes_input_pointer(char *s) {
+  char *original = s;
+  strsep(&s, ""); // no-warning
+  clang_analyzer_eval(s == original); // expected-warning{{UNKNOWN}}
+  clang_analyzer_eval(s == NULL); // expected-warning{{UNKNOWN}}
+
+  // Check that the value is symbolic.
+  if (s == NULL) {
+    clang_analyzer_eval(s == NULL); // expected-warning{{TRUE}}
+  }
+}
+
+void strsep_changes_input_string() {
+  char str[] = "abc";
+
+  clang_analyzer_eval(str[1] == 'b'); // expected-warning{{TRUE}}
+
+  char *s = str;
+  strsep(&s, "b"); // no-warning
+
+  // The real strsep will change the first delimiter it finds into a NUL
+  // character. For now, we just model the invalidation.
+  clang_analyzer_eval(str[1] == 'b'); // expected-warning{{UNKNOWN}}
+}
+
+//===----------------------------------------------------------------------===
 // FIXMEs
 //===----------------------------------------------------------------------===
 





More information about the cfe-commits mailing list