]> granicus.if.org Git - clang/commitdiff
[analyzer] Model strsep(), particularly that it returns its input.
authorJordan Rose <jordan_rose@apple.com>
Mon, 22 Apr 2013 23:18:42 +0000 (23:18 +0000)
committerJordan Rose <jordan_rose@apple.com>
Mon, 22 Apr 2013 23:18:42 +0000 (23:18 +0000)
This handles the false positive leak warning in PR15374, and also serves
as a basic model for the strsep() function.

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@180069 91177308-0d34-0410-b5e6-96231b3b80d8

lib/StaticAnalyzer/Checkers/CStringChecker.cpp
test/Analysis/string.c

index cc55e9f6ecf0651902246d52165bc06863863750..aa1ca6f2f809d5dddeae2eec5b043f52f261fb96 100644 (file)
@@ -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(CheckerContext &C, const CallExpr *CE,
   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 CallExpr *CE, CheckerContext &C) const {
   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 CallExpr *CE, CheckerContext &C) const {
     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"))
index 74cf33c4bc224fb7e4e8b8fbf80c2bb8921fff4c..6cf52f7a557b2fde0144d3f5517356e62ac12cd2 100644 (file)
@@ -1027,6 +1027,57 @@ void strncasecmp_embedded_null () {
        clang_analyzer_eval(strncasecmp("ab\0zz", "ab\0yy", 4) == 0); // expected-warning{{TRUE}}
 }
 
+//===----------------------------------------------------------------------===
+// 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
 //===----------------------------------------------------------------------===