[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:
Deep Majumder 2021-07-16 12:34:30 +05:30
parent af9321739b
commit 13fe78212f
3 changed files with 123 additions and 0 deletions

View File

@ -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();

View File

@ -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);

View File

@ -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