Skip to content

Commit 6e3cf2b

Browse files
committed
[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. llvm-svn: 180069
1 parent 4a4970e commit 6e3cf2b

File tree

2 files changed

+113
-0
lines changed

2 files changed

+113
-0
lines changed

clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ class CStringChecker : public Checker< eval::Call,
114114
bool isBounded = false,
115115
bool ignoreCase = false) const;
116116

117+
void evalStrsep(CheckerContext &C, const CallExpr *CE) const;
118+
117119
// Utility methods
118120
std::pair<ProgramStateRef , ProgramStateRef >
119121
static assumeZero(CheckerContext &C,
@@ -1752,6 +1754,63 @@ void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE,
17521754
C.addTransition(state);
17531755
}
17541756

1757+
void CStringChecker::evalStrsep(CheckerContext &C, const CallExpr *CE) const {
1758+
//char *strsep(char **stringp, const char *delim);
1759+
if (CE->getNumArgs() < 2)
1760+
return;
1761+
1762+
// Sanity: does the search string parameter match the return type?
1763+
const Expr *SearchStrPtr = CE->getArg(0);
1764+
QualType CharPtrTy = SearchStrPtr->getType()->getPointeeType();
1765+
if (CharPtrTy.isNull() ||
1766+
CE->getType().getUnqualifiedType() != CharPtrTy.getUnqualifiedType())
1767+
return;
1768+
1769+
CurrentFunctionDescription = "strsep()";
1770+
ProgramStateRef State = C.getState();
1771+
const LocationContext *LCtx = C.getLocationContext();
1772+
1773+
// Check that the search string pointer is non-null (though it may point to
1774+
// a null string).
1775+
SVal SearchStrVal = State->getSVal(SearchStrPtr, LCtx);
1776+
State = checkNonNull(C, State, SearchStrPtr, SearchStrVal);
1777+
if (!State)
1778+
return;
1779+
1780+
// Check that the delimiter string is non-null.
1781+
const Expr *DelimStr = CE->getArg(1);
1782+
SVal DelimStrVal = State->getSVal(DelimStr, LCtx);
1783+
State = checkNonNull(C, State, DelimStr, DelimStrVal);
1784+
if (!State)
1785+
return;
1786+
1787+
SValBuilder &SVB = C.getSValBuilder();
1788+
SVal Result;
1789+
if (Optional<Loc> SearchStrLoc = SearchStrVal.getAs<Loc>()) {
1790+
// Get the current value of the search string pointer, as a char*.
1791+
Result = State->getSVal(*SearchStrLoc, CharPtrTy);
1792+
1793+
// Invalidate the search string, representing the change of one delimiter
1794+
// character to NUL.
1795+
State = InvalidateBuffer(C, State, SearchStrPtr, Result);
1796+
1797+
// Overwrite the search string pointer. The new value is either an address
1798+
// further along in the same string, or NULL if there are no more tokens.
1799+
State = State->bindLoc(*SearchStrLoc,
1800+
SVB.conjureSymbolVal(getTag(), CE, LCtx, CharPtrTy,
1801+
C.blockCount()));
1802+
} else {
1803+
assert(SearchStrVal.isUnknown());
1804+
// Conjure a symbolic value. It's the best we can do.
1805+
Result = SVB.conjureSymbolVal(0, CE, LCtx, C.blockCount());
1806+
}
1807+
1808+
// Set the return value, and finish.
1809+
State = State->BindExpr(CE, LCtx, Result);
1810+
C.addTransition(State);
1811+
}
1812+
1813+
17551814
//===----------------------------------------------------------------------===//
17561815
// The driver method, and other Checker callbacks.
17571816
//===----------------------------------------------------------------------===//
@@ -1762,6 +1821,7 @@ bool CStringChecker::evalCall(const CallExpr *CE, CheckerContext &C) const {
17621821
if (!FDecl)
17631822
return false;
17641823

1824+
// FIXME: Poorly-factored string switches are slow.
17651825
FnCheck evalFunction = 0;
17661826
if (C.isCLibraryFunction(FDecl, "memcpy"))
17671827
evalFunction = &CStringChecker::evalMemcpy;
@@ -1793,6 +1853,8 @@ bool CStringChecker::evalCall(const CallExpr *CE, CheckerContext &C) const {
17931853
evalFunction = &CStringChecker::evalStrcasecmp;
17941854
else if (C.isCLibraryFunction(FDecl, "strncasecmp"))
17951855
evalFunction = &CStringChecker::evalStrncasecmp;
1856+
else if (C.isCLibraryFunction(FDecl, "strsep"))
1857+
evalFunction = &CStringChecker::evalStrsep;
17961858
else if (C.isCLibraryFunction(FDecl, "bcopy"))
17971859
evalFunction = &CStringChecker::evalBcopy;
17981860
else if (C.isCLibraryFunction(FDecl, "bcmp"))

clang/test/Analysis/string.c

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1027,6 +1027,57 @@ void strncasecmp_embedded_null () {
10271027
clang_analyzer_eval(strncasecmp("ab\0zz", "ab\0yy", 4) == 0); // expected-warning{{TRUE}}
10281028
}
10291029

1030+
//===----------------------------------------------------------------------===
1031+
// strsep()
1032+
//===----------------------------------------------------------------------===
1033+
1034+
char *strsep(char **stringp, const char *delim);
1035+
1036+
void strsep_null_delim(char *s) {
1037+
strsep(&s, NULL); // expected-warning{{Null pointer argument in call to strsep()}}
1038+
}
1039+
1040+
void strsep_null_search() {
1041+
strsep(NULL, ""); // expected-warning{{Null pointer argument in call to strsep()}}
1042+
}
1043+
1044+
void strsep_return_original_pointer(char *s) {
1045+
char *original = s;
1046+
char *result = strsep(&s, ""); // no-warning
1047+
clang_analyzer_eval(original == result); // expected-warning{{TRUE}}
1048+
}
1049+
1050+
void strsep_null_string() {
1051+
char *s = NULL;
1052+
char *result = strsep(&s, ""); // no-warning
1053+
clang_analyzer_eval(result == NULL); // expected-warning{{TRUE}}
1054+
}
1055+
1056+
void strsep_changes_input_pointer(char *s) {
1057+
char *original = s;
1058+
strsep(&s, ""); // no-warning
1059+
clang_analyzer_eval(s == original); // expected-warning{{UNKNOWN}}
1060+
clang_analyzer_eval(s == NULL); // expected-warning{{UNKNOWN}}
1061+
1062+
// Check that the value is symbolic.
1063+
if (s == NULL) {
1064+
clang_analyzer_eval(s == NULL); // expected-warning{{TRUE}}
1065+
}
1066+
}
1067+
1068+
void strsep_changes_input_string() {
1069+
char str[] = "abc";
1070+
1071+
clang_analyzer_eval(str[1] == 'b'); // expected-warning{{TRUE}}
1072+
1073+
char *s = str;
1074+
strsep(&s, "b"); // no-warning
1075+
1076+
// The real strsep will change the first delimiter it finds into a NUL
1077+
// character. For now, we just model the invalidation.
1078+
clang_analyzer_eval(str[1] == 'b'); // expected-warning{{UNKNOWN}}
1079+
}
1080+
10301081
//===----------------------------------------------------------------------===
10311082
// FIXMEs
10321083
//===----------------------------------------------------------------------===

0 commit comments

Comments
 (0)