[analyzer] Invalidate destination of std::copy() and std::copy_backward().

Now that the libcpp implementations of these methods has a branch that doesn't call
memmove(), the analyzer needs to invalidate the destination for these methods explicitly.

rdar://problem/23575656

llvm-svn: 260043
This commit is contained in:
Devin Coughlin 2016-02-07 16:55:44 +00:00
parent 656b3b4c5d
commit 9165df129e
8 changed files with 241 additions and 37 deletions

View File

@ -201,6 +201,10 @@ public:
}
return static_cast<T*>(data);
}
/// Returns true if the root namespace of the given declaration is the 'std'
/// C++ namespace.
static bool isInStdNamespace(const Decl *D);
private:
ManagedAnalysis *&getAnalysisImpl(const void* tag);

View File

@ -317,6 +317,21 @@ AnalysisDeclContext::getBlockInvocationContext(const LocationContext *parent,
BD, ContextData);
}
bool AnalysisDeclContext::isInStdNamespace(const Decl *D) {
const DeclContext *DC = D->getDeclContext()->getEnclosingNamespaceContext();
const NamespaceDecl *ND = dyn_cast<NamespaceDecl>(DC);
if (!ND)
return false;
while (const DeclContext *Parent = ND->getParent()) {
if (!isa<NamespaceDecl>(Parent))
break;
ND = cast<NamespaceDecl>(Parent);
}
return ND->isStdNamespace();
}
LocationContextManager & AnalysisDeclContext::getLocationContextManager() {
assert(Manager &&
"Cannot create LocationContexts without an AnalysisDeclContextManager!");

View File

@ -118,6 +118,10 @@ public:
void evalStrsep(CheckerContext &C, const CallExpr *CE) const;
void evalStdCopy(CheckerContext &C, const CallExpr *CE) const;
void evalStdCopyBackward(CheckerContext &C, const CallExpr *CE) const;
void evalStdCopyCommon(CheckerContext &C, const CallExpr *CE) const;
// Utility methods
std::pair<ProgramStateRef , ProgramStateRef >
static assumeZero(CheckerContext &C,
@ -1950,7 +1954,57 @@ void CStringChecker::evalStrsep(CheckerContext &C, const CallExpr *CE) const {
C.addTransition(State);
}
// These should probably be moved into a C++ standard library checker.
void CStringChecker::evalStdCopy(CheckerContext &C, const CallExpr *CE) const {
evalStdCopyCommon(C, CE);
}
void CStringChecker::evalStdCopyBackward(CheckerContext &C,
const CallExpr *CE) const {
evalStdCopyCommon(C, CE);
}
void CStringChecker::evalStdCopyCommon(CheckerContext &C,
const CallExpr *CE) const {
if (CE->getNumArgs() < 3)
return;
ProgramStateRef State = C.getState();
const LocationContext *LCtx = C.getLocationContext();
// template <class _InputIterator, class _OutputIterator>
// _OutputIterator
// copy(_InputIterator __first, _InputIterator __last,
// _OutputIterator __result)
// Invalidate the destination buffer
const Expr *Dst = CE->getArg(2);
SVal DstVal = State->getSVal(Dst, LCtx);
State = InvalidateBuffer(C, State, Dst, DstVal, /*IsSource=*/false,
/*Size=*/nullptr);
SValBuilder &SVB = C.getSValBuilder();
SVal ResultVal = SVB.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount());
State = State->BindExpr(CE, LCtx, ResultVal);
C.addTransition(State);
}
static bool isCPPStdLibraryFunction(const FunctionDecl *FD, StringRef Name) {
IdentifierInfo *II = FD->getIdentifier();
if (!II)
return false;
if (!AnalysisDeclContext::isInStdNamespace(FD))
return false;
if (II->getName().equals(Name))
return true;
return false;
}
//===----------------------------------------------------------------------===//
// The driver method, and other Checker callbacks.
//===----------------------------------------------------------------------===//
@ -1999,6 +2053,10 @@ bool CStringChecker::evalCall(const CallExpr *CE, CheckerContext &C) const {
evalFunction = &CStringChecker::evalBcopy;
else if (C.isCLibraryFunction(FDecl, "bcmp"))
evalFunction = &CStringChecker::evalMemcmp;
else if (isCPPStdLibraryFunction(FDecl, "copy"))
evalFunction = &CStringChecker::evalStdCopy;
else if (isCPPStdLibraryFunction(FDecl, "copy_backward"))
evalFunction = &CStringChecker::evalStdCopyBackward;
// If the callee isn't a string function, let another checker handle it.
if (!evalFunction)

View File

@ -1540,20 +1540,6 @@ ConditionBRVisitor::VisitTrueTest(const Expr *Cond,
return event;
}
// FIXME: Copied from ExprEngineCallAndReturn.cpp.
static bool isInStdNamespace(const Decl *D) {
const DeclContext *DC = D->getDeclContext()->getEnclosingNamespaceContext();
const NamespaceDecl *ND = dyn_cast<NamespaceDecl>(DC);
if (!ND)
return false;
while (const NamespaceDecl *Parent = dyn_cast<NamespaceDecl>(ND->getParent()))
ND = Parent;
return ND->isStdNamespace();
}
std::unique_ptr<PathDiagnosticPiece>
LikelyFalsePositiveSuppressionBRVisitor::getEndPath(BugReporterContext &BRC,
const ExplodedNode *N,
@ -1564,7 +1550,7 @@ LikelyFalsePositiveSuppressionBRVisitor::getEndPath(BugReporterContext &BRC,
AnalyzerOptions &Options = Eng.getAnalysisManager().options;
const Decl *D = N->getLocationContext()->getDecl();
if (isInStdNamespace(D)) {
if (AnalysisDeclContext::isInStdNamespace(D)) {
// Skip reports within the 'std' namespace. Although these can sometimes be
// the user's fault, we currently don't report them very well, and
// Note that this will not help for any other data structure libraries, like

View File

@ -382,21 +382,6 @@ void ExprEngine::examineStackFrames(const Decl *D, const LocationContext *LCtx,
}
static bool IsInStdNamespace(const FunctionDecl *FD) {
const DeclContext *DC = FD->getEnclosingNamespaceContext();
const NamespaceDecl *ND = dyn_cast<NamespaceDecl>(DC);
if (!ND)
return false;
while (const DeclContext *Parent = ND->getParent()) {
if (!isa<NamespaceDecl>(Parent))
break;
ND = cast<NamespaceDecl>(Parent);
}
return ND->isStdNamespace();
}
// The GDM component containing the dynamic dispatch bifurcation info. When
// the exact type of the receiver is not known, we want to explore both paths -
// one on which we do inline it and the other one on which we don't. This is
@ -761,7 +746,7 @@ static bool mayInlineDecl(AnalysisDeclContext *CalleeADC,
// Conditionally control the inlining of C++ standard library functions.
if (!Opts.mayInlineCXXStandardLibrary())
if (Ctx.getSourceManager().isInSystemHeader(FD->getLocation()))
if (IsInStdNamespace(FD))
if (AnalysisDeclContext::isInStdNamespace(FD))
return false;
// Conditionally control the inlining of methods on objects that look

View File

@ -7,6 +7,9 @@
typedef unsigned char uint8_t;
typedef __typeof__(sizeof(int)) size_t;
void *memmove(void *s1, const void *s2, size_t n);
namespace std {
template <class T1, class T2>
struct pair {
@ -104,11 +107,120 @@ namespace std {
const _E* end() const {return __begin_ + __size_;}
};
template <bool, class _Tp = void> struct enable_if {};
template <class _Tp> struct enable_if<true, _Tp> {typedef _Tp type;};
template <class _Tp, _Tp __v>
struct integral_constant
{
static const _Tp value = __v;
typedef _Tp value_type;
typedef integral_constant type;
operator value_type() const {return value;}
value_type operator ()() const {return value;}
};
template <class _Tp, _Tp __v>
const _Tp integral_constant<_Tp, __v>::value;
template <class _Tp, class _Arg>
struct is_trivially_assignable
: integral_constant<bool, __is_trivially_assignable(_Tp, _Arg)>
{
};
typedef integral_constant<bool,true> true_type;
typedef integral_constant<bool,false> false_type;
template <class _Tp> struct is_const : public false_type {};
template <class _Tp> struct is_const<_Tp const> : public true_type {};
template <class _Tp> struct is_reference : public false_type {};
template <class _Tp> struct is_reference<_Tp&> : public true_type {};
template <class _Tp, class _Up> struct is_same : public false_type {};
template <class _Tp> struct is_same<_Tp, _Tp> : public true_type {};
template <class _Tp, bool = is_const<_Tp>::value || is_reference<_Tp>::value >
struct __add_const {typedef _Tp type;};
template <class _Tp>
struct __add_const<_Tp, false> {typedef const _Tp type;};
template <class _Tp> struct add_const {typedef typename __add_const<_Tp>::type type;};
template <class _Tp> struct remove_const {typedef _Tp type;};
template <class _Tp> struct remove_const<const _Tp> {typedef _Tp type;};
template <class _Tp> struct add_lvalue_reference {typedef _Tp& type;};
template <class _Tp> struct is_trivially_copy_assignable
: public is_trivially_assignable<typename add_lvalue_reference<_Tp>::type,
typename add_lvalue_reference<typename add_const<_Tp>::type>::type> {};
template<class InputIter, class OutputIter>
OutputIter __copy(InputIter II, InputIter IE, OutputIter OI) {
while (II != IE)
*OI++ = *II++;
return OI;
}
template <class _Tp, class _Up>
inline
typename enable_if
<
is_same<typename remove_const<_Tp>::type, _Up>::value &&
is_trivially_copy_assignable<_Up>::value,
_Up*
>::type __copy(_Tp* __first, _Tp* __last, _Up* __result) {
size_t __n = __last - __first;
if (__n > 0)
memmove(__result, __first, __n * sizeof(_Up));
return __result + __n;
}
template<class InputIter, class OutputIter>
OutputIter copy(InputIter II, InputIter IE, OutputIter OI) {
while (II != IE)
*OI++ = *II++;
return OI;
return __copy(II, IE, OI);
}
template <class _BidirectionalIterator, class _OutputIterator>
inline
_OutputIterator
__copy_backward(_BidirectionalIterator __first, _BidirectionalIterator __last,
_OutputIterator __result)
{
while (__first != __last)
*--__result = *--__last;
return __result;
}
template <class _Tp, class _Up>
inline
typename enable_if
<
is_same<typename remove_const<_Tp>::type, _Up>::value &&
is_trivially_copy_assignable<_Up>::value,
_Up*
>::type __copy_backward(_Tp* __first, _Tp* __last, _Up* __result) {
size_t __n = __last - __first;
if (__n > 0)
{
__result -= __n;
memmove(__result, __first, __n * sizeof(_Up));
}
return __result;
}
template<class InputIter, class OutputIter>
OutputIter copy_backward(InputIter II, InputIter IE, OutputIter OI) {
return __copy_backward(II, IE, OI);
}
struct input_iterator_tag { };

View File

@ -0,0 +1,38 @@
// RUN: %clang_cc1 -analyze -analyzer-checker=core,unix.cstring,alpha.unix.cstring,debug.ExprInspection -analyzer-store=region -verify %s
#include "Inputs/system-header-simulator-cxx.h"
#include "Inputs/system-header-simulator-for-malloc.h"
void clang_analyzer_eval(int);
int *testStdCopyInvalidatesBuffer(std::vector<int> v) {
int n = v.size();
int *buf = (int *)malloc(n * sizeof(int));
buf[0] = 66;
// Call to copy should invalidate buf.
std::copy(v.begin(), v.end(), buf);
int i = buf[0];
clang_analyzer_eval(i == 66); // expected-warning {{UNKNOWN}}
return buf;
}
int *testStdCopyBackwardInvalidatesBuffer(std::vector<int> v) {
int n = v.size();
int *buf = (int *)malloc(n * sizeof(int));
buf[0] = 66;
// Call to copy_backward should invalidate buf.
std::copy_backward(v.begin(), v.end(), buf + n);
int i = buf[0];
clang_analyzer_eval(i == 66); // expected-warning {{UNKNOWN}}
return buf;
}

View File

@ -9,9 +9,15 @@
void clang_analyzer_eval(bool);
void testCopyNull(int *I, int *E) {
std::copy(I, E, (int *)0);
class C {
// The virtual function is to make C not trivially copy assignable so that we call the
// variant of std::copy() that does not defer to memmove().
virtual int f();
};
void testCopyNull(C *I, C *E) {
std::copy(I, E, (C *)0);
#ifndef SUPPRESSED
// expected-warning@../Inputs/system-header-simulator-cxx.h:110 {{Dereference of null pointer}}
// expected-warning@../Inputs/system-header-simulator-cxx.h:166 {{Called C++ object pointer is null}}
#endif
}