[analyzer] Handle << operator for std::unique_ptr
This patch handles the `<<` operator defined for `std::unique_ptr` in the std namespace (ignores custom overloads of the operator). Differential Revision: https://reviews.llvm.org/D105421
This commit is contained in:
parent
af9321739b
commit
13fe78212f
|
@ -72,6 +72,7 @@ private:
|
|||
const MemRegion *OtherSmartPtrRegion) const;
|
||||
void handleBoolConversion(const CallEvent &Call, CheckerContext &C) const;
|
||||
bool handleComparisionOp(const CallEvent &Call, CheckerContext &C) const;
|
||||
bool handleOstreamOperator(const CallEvent &Call, CheckerContext &C) const;
|
||||
std::pair<SVal, ProgramStateRef>
|
||||
retrieveOrConjureInnerPtrVal(ProgramStateRef State,
|
||||
const MemRegion *ThisRegion, const Expr *E,
|
||||
|
@ -89,6 +90,31 @@ private:
|
|||
|
||||
REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, SVal)
|
||||
|
||||
// Checks if RD has name in Names and is in std namespace
|
||||
static bool hasStdClassWithName(const CXXRecordDecl *RD,
|
||||
ArrayRef<llvm::StringLiteral> Names) {
|
||||
if (!RD || !RD->getDeclContext()->isStdNamespace())
|
||||
return false;
|
||||
if (RD->getDeclName().isIdentifier()) {
|
||||
StringRef Name = RD->getName();
|
||||
return llvm::any_of(Names, [&Name](StringRef GivenName) -> bool {
|
||||
return Name == GivenName;
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr llvm::StringLiteral STD_PTR_NAMES[] = {"shared_ptr", "unique_ptr",
|
||||
"weak_ptr"};
|
||||
|
||||
static bool isStdSmartPtr(const CXXRecordDecl *RD) {
|
||||
return hasStdClassWithName(RD, STD_PTR_NAMES);
|
||||
}
|
||||
|
||||
static bool isStdSmartPtr(const Expr *E) {
|
||||
return isStdSmartPtr(E->getType()->getAsCXXRecordDecl());
|
||||
}
|
||||
|
||||
// Define the inter-checker API.
|
||||
namespace clang {
|
||||
namespace ento {
|
||||
|
@ -193,6 +219,30 @@ bool SmartPtrModeling::isBoolConversionMethod(const CallEvent &Call) const {
|
|||
return CD && CD->getConversionType()->isBooleanType();
|
||||
}
|
||||
|
||||
constexpr llvm::StringLiteral BASIC_OSTREAM_NAMES[] = {"basic_ostream"};
|
||||
|
||||
bool isStdBasicOstream(const Expr *E) {
|
||||
const auto *RD = E->getType()->getAsCXXRecordDecl();
|
||||
return hasStdClassWithName(RD, BASIC_OSTREAM_NAMES);
|
||||
}
|
||||
|
||||
bool isStdOstreamOperatorCall(const CallEvent &Call) {
|
||||
if (Call.getNumArgs() != 2 ||
|
||||
!Call.getDecl()->getDeclContext()->isStdNamespace())
|
||||
return false;
|
||||
const auto *FC = dyn_cast<SimpleFunctionCall>(&Call);
|
||||
if (!FC)
|
||||
return false;
|
||||
const FunctionDecl *FD = FC->getDecl();
|
||||
if (!FD->isOverloadedOperator())
|
||||
return false;
|
||||
const OverloadedOperatorKind OOK = FD->getOverloadedOperator();
|
||||
if (OOK != clang::OO_LessLess)
|
||||
return false;
|
||||
return isStdSmartPtr(Call.getArgExpr(1)) &&
|
||||
isStdBasicOstream(Call.getArgExpr(0));
|
||||
}
|
||||
|
||||
bool SmartPtrModeling::evalCall(const CallEvent &Call,
|
||||
CheckerContext &C) const {
|
||||
ProgramStateRef State = C.getState();
|
||||
|
@ -206,6 +256,9 @@ bool SmartPtrModeling::evalCall(const CallEvent &Call,
|
|||
if (handleComparisionOp(Call, C))
|
||||
return true;
|
||||
|
||||
if (isStdOstreamOperatorCall(Call))
|
||||
return handleOstreamOperator(Call, C);
|
||||
|
||||
if (!smartptr::isStdSmartPtrCall(Call))
|
||||
return false;
|
||||
|
||||
|
@ -378,6 +431,30 @@ bool SmartPtrModeling::handleComparisionOp(const CallEvent &Call,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool SmartPtrModeling::handleOstreamOperator(const CallEvent &Call,
|
||||
CheckerContext &C) const {
|
||||
// operator<< does not modify the smart pointer.
|
||||
// And we don't really have much of modelling of basic_ostream.
|
||||
// So, we are better off:
|
||||
// 1) Invalidating the mem-region of the ostream object at hand.
|
||||
// 2) Setting the SVal of the basic_ostream as the return value.
|
||||
// Not very satisfying, but it gets the job done, and is better
|
||||
// than the default handling. :)
|
||||
|
||||
ProgramStateRef State = C.getState();
|
||||
const auto StreamVal = Call.getArgSVal(0);
|
||||
const MemRegion *StreamThisRegion = StreamVal.getAsRegion();
|
||||
if (!StreamThisRegion)
|
||||
return false;
|
||||
State =
|
||||
State->invalidateRegions({StreamThisRegion}, Call.getOriginExpr(),
|
||||
C.blockCount(), C.getLocationContext(), false);
|
||||
State =
|
||||
State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), StreamVal);
|
||||
C.addTransition(State);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SmartPtrModeling::checkDeadSymbols(SymbolReaper &SymReaper,
|
||||
CheckerContext &C) const {
|
||||
ProgramStateRef State = C.getState();
|
||||
|
|
|
@ -1036,6 +1036,22 @@ bool operator<=(nullptr_t x, const unique_ptr<T> &y);
|
|||
} // namespace std
|
||||
#endif
|
||||
|
||||
namespace std {
|
||||
template <class CharT>
|
||||
class basic_ostream;
|
||||
|
||||
using ostream = basic_ostream<char>;
|
||||
|
||||
extern std::ostream cout;
|
||||
|
||||
ostream &operator<<(ostream &, const string &);
|
||||
|
||||
#if __cplusplus >= 202002L
|
||||
template <class T>
|
||||
ostream &operator<<(ostream &, const std::unique_ptr<T> &);
|
||||
#endif
|
||||
} // namespace std
|
||||
|
||||
#ifdef TEST_INLINABLE_ALLOCATORS
|
||||
namespace std {
|
||||
void *malloc(size_t);
|
||||
|
|
|
@ -3,6 +3,11 @@
|
|||
// RUN: -analyzer-config cplusplus.SmartPtrModeling:ModelSmartPtrDereference=true\
|
||||
// RUN: -std=c++11 -verify %s
|
||||
|
||||
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection\
|
||||
// RUN: -analyzer-checker cplusplus.Move,alpha.cplusplus.SmartPtr\
|
||||
// RUN: -analyzer-config cplusplus.SmartPtrModeling:ModelSmartPtrDereference=true\
|
||||
// RUN: -std=c++20 -verify %s
|
||||
|
||||
#include "Inputs/system-header-simulator-cxx.h"
|
||||
|
||||
void clang_analyzer_warnIfReached();
|
||||
|
@ -506,3 +511,28 @@ void uniquePtrComparisionDifferingTypes(std::unique_ptr<int> unknownPtr) {
|
|||
clang_analyzer_eval(nullPtr != nullptr); // expected-warning{{FALSE}}
|
||||
clang_analyzer_eval(nullptr <= unknownPtr); // expected-warning{{TRUE}}
|
||||
}
|
||||
|
||||
#if __cplusplus >= 202002L
|
||||
|
||||
void testOstreamOverload(std::unique_ptr<int> P) {
|
||||
auto &Cout = std::cout;
|
||||
auto &PtrCout = std::cout << P;
|
||||
auto &StringCout = std::cout << "hello";
|
||||
// We are testing the fact that in our modelling of
|
||||
// operator<<(basic_ostream<T1> &, const unique_ptr<T2> &)
|
||||
// we set the return SVal to the SVal of the ostream arg.
|
||||
clang_analyzer_eval(&Cout == &PtrCout); // expected-warning {{TRUE}}
|
||||
// FIXME: Technically, they should be equal,
|
||||
// that hasn't been modelled yet.
|
||||
clang_analyzer_eval(&Cout == &StringCout); // expected-warning {{UNKNOWN}}
|
||||
}
|
||||
|
||||
int glob;
|
||||
void testOstreamDoesntInvalidateGlobals(std::unique_ptr<int> P) {
|
||||
int x = glob;
|
||||
std::cout << P;
|
||||
int y = glob;
|
||||
clang_analyzer_eval(x == y); // expected-warning {{TRUE}}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue