313 lines
12 KiB
C++
313 lines
12 KiB
C++
|
//=== InnerPointerChecker.cpp -------------------------------------*- C++ -*--//
|
||
|
//
|
||
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||
|
// See https://llvm.org/LICENSE.txt for license information.
|
||
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||
|
//
|
||
|
//===----------------------------------------------------------------------===//
|
||
|
//
|
||
|
// This file defines a check that marks a raw pointer to a C++ container's
|
||
|
// inner buffer released when the object is destroyed. This information can
|
||
|
// be used by MallocChecker to detect use-after-free problems.
|
||
|
//
|
||
|
//===----------------------------------------------------------------------===//
|
||
|
|
||
|
#include "AllocationState.h"
|
||
|
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
|
||
|
#include "InterCheckerAPI.h"
|
||
|
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
|
||
|
#include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h"
|
||
|
#include "clang/StaticAnalyzer/Core/Checker.h"
|
||
|
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
|
||
|
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
|
||
|
|
||
|
using namespace clang;
|
||
|
using namespace ento;
|
||
|
|
||
|
// Associate container objects with a set of raw pointer symbols.
|
||
|
REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(PtrSet, SymbolRef)
|
||
|
REGISTER_MAP_WITH_PROGRAMSTATE(RawPtrMap, const MemRegion *, PtrSet)
|
||
|
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
class InnerPointerChecker
|
||
|
: public Checker<check::DeadSymbols, check::PostCall> {
|
||
|
|
||
|
CallDescription AppendFn, AssignFn, ClearFn, CStrFn, DataFn, EraseFn,
|
||
|
InsertFn, PopBackFn, PushBackFn, ReplaceFn, ReserveFn, ResizeFn,
|
||
|
ShrinkToFitFn, SwapFn;
|
||
|
|
||
|
public:
|
||
|
class InnerPointerBRVisitor : public BugReporterVisitor {
|
||
|
SymbolRef PtrToBuf;
|
||
|
|
||
|
public:
|
||
|
InnerPointerBRVisitor(SymbolRef Sym) : PtrToBuf(Sym) {}
|
||
|
|
||
|
static void *getTag() {
|
||
|
static int Tag = 0;
|
||
|
return &Tag;
|
||
|
}
|
||
|
|
||
|
void Profile(llvm::FoldingSetNodeID &ID) const override {
|
||
|
ID.AddPointer(getTag());
|
||
|
}
|
||
|
|
||
|
virtual PathDiagnosticPieceRef
|
||
|
VisitNode(const ExplodedNode *N, BugReporterContext &BRC,
|
||
|
PathSensitiveBugReport &BR) override;
|
||
|
|
||
|
// FIXME: Scan the map once in the visitor's constructor and do a direct
|
||
|
// lookup by region.
|
||
|
bool isSymbolTracked(ProgramStateRef State, SymbolRef Sym) {
|
||
|
RawPtrMapTy Map = State->get<RawPtrMap>();
|
||
|
for (const auto &Entry : Map) {
|
||
|
if (Entry.second.contains(Sym))
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
InnerPointerChecker()
|
||
|
: AppendFn({"std", "basic_string", "append"}),
|
||
|
AssignFn({"std", "basic_string", "assign"}),
|
||
|
ClearFn({"std", "basic_string", "clear"}),
|
||
|
CStrFn({"std", "basic_string", "c_str"}),
|
||
|
DataFn({"std", "basic_string", "data"}),
|
||
|
EraseFn({"std", "basic_string", "erase"}),
|
||
|
InsertFn({"std", "basic_string", "insert"}),
|
||
|
PopBackFn({"std", "basic_string", "pop_back"}),
|
||
|
PushBackFn({"std", "basic_string", "push_back"}),
|
||
|
ReplaceFn({"std", "basic_string", "replace"}),
|
||
|
ReserveFn({"std", "basic_string", "reserve"}),
|
||
|
ResizeFn({"std", "basic_string", "resize"}),
|
||
|
ShrinkToFitFn({"std", "basic_string", "shrink_to_fit"}),
|
||
|
SwapFn({"std", "basic_string", "swap"}) {}
|
||
|
|
||
|
/// Check whether the called member function potentially invalidates
|
||
|
/// pointers referring to the container object's inner buffer.
|
||
|
bool isInvalidatingMemberFunction(const CallEvent &Call) const;
|
||
|
|
||
|
/// Mark pointer symbols associated with the given memory region released
|
||
|
/// in the program state.
|
||
|
void markPtrSymbolsReleased(const CallEvent &Call, ProgramStateRef State,
|
||
|
const MemRegion *ObjRegion,
|
||
|
CheckerContext &C) const;
|
||
|
|
||
|
/// Standard library functions that take a non-const `basic_string` argument by
|
||
|
/// reference may invalidate its inner pointers. Check for these cases and
|
||
|
/// mark the pointers released.
|
||
|
void checkFunctionArguments(const CallEvent &Call, ProgramStateRef State,
|
||
|
CheckerContext &C) const;
|
||
|
|
||
|
/// Record the connection between raw pointers referring to a container
|
||
|
/// object's inner buffer and the object's memory region in the program state.
|
||
|
/// Mark potentially invalidated pointers released.
|
||
|
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
|
||
|
|
||
|
/// Clean up the program state map.
|
||
|
void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
|
||
|
};
|
||
|
|
||
|
} // end anonymous namespace
|
||
|
|
||
|
bool InnerPointerChecker::isInvalidatingMemberFunction(
|
||
|
const CallEvent &Call) const {
|
||
|
if (const auto *MemOpCall = dyn_cast<CXXMemberOperatorCall>(&Call)) {
|
||
|
OverloadedOperatorKind Opc = MemOpCall->getOriginExpr()->getOperator();
|
||
|
if (Opc == OO_Equal || Opc == OO_PlusEqual)
|
||
|
return true;
|
||
|
return false;
|
||
|
}
|
||
|
return (isa<CXXDestructorCall>(Call) || Call.isCalled(AppendFn) ||
|
||
|
Call.isCalled(AssignFn) || Call.isCalled(ClearFn) ||
|
||
|
Call.isCalled(EraseFn) || Call.isCalled(InsertFn) ||
|
||
|
Call.isCalled(PopBackFn) || Call.isCalled(PushBackFn) ||
|
||
|
Call.isCalled(ReplaceFn) || Call.isCalled(ReserveFn) ||
|
||
|
Call.isCalled(ResizeFn) || Call.isCalled(ShrinkToFitFn) ||
|
||
|
Call.isCalled(SwapFn));
|
||
|
}
|
||
|
|
||
|
void InnerPointerChecker::markPtrSymbolsReleased(const CallEvent &Call,
|
||
|
ProgramStateRef State,
|
||
|
const MemRegion *MR,
|
||
|
CheckerContext &C) const {
|
||
|
if (const PtrSet *PS = State->get<RawPtrMap>(MR)) {
|
||
|
const Expr *Origin = Call.getOriginExpr();
|
||
|
for (const auto Symbol : *PS) {
|
||
|
// NOTE: `Origin` may be null, and will be stored so in the symbol's
|
||
|
// `RefState` in MallocChecker's `RegionState` program state map.
|
||
|
State = allocation_state::markReleased(State, Symbol, Origin);
|
||
|
}
|
||
|
State = State->remove<RawPtrMap>(MR);
|
||
|
C.addTransition(State);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void InnerPointerChecker::checkFunctionArguments(const CallEvent &Call,
|
||
|
ProgramStateRef State,
|
||
|
CheckerContext &C) const {
|
||
|
if (const auto *FC = dyn_cast<AnyFunctionCall>(&Call)) {
|
||
|
const FunctionDecl *FD = FC->getDecl();
|
||
|
if (!FD || !FD->isInStdNamespace())
|
||
|
return;
|
||
|
|
||
|
for (unsigned I = 0, E = FD->getNumParams(); I != E; ++I) {
|
||
|
QualType ParamTy = FD->getParamDecl(I)->getType();
|
||
|
if (!ParamTy->isReferenceType() ||
|
||
|
ParamTy->getPointeeType().isConstQualified())
|
||
|
continue;
|
||
|
|
||
|
// In case of member operator calls, `this` is counted as an
|
||
|
// argument but not as a parameter.
|
||
|
bool isaMemberOpCall = isa<CXXMemberOperatorCall>(FC);
|
||
|
unsigned ArgI = isaMemberOpCall ? I+1 : I;
|
||
|
|
||
|
SVal Arg = FC->getArgSVal(ArgI);
|
||
|
const auto *ArgRegion =
|
||
|
dyn_cast_or_null<TypedValueRegion>(Arg.getAsRegion());
|
||
|
if (!ArgRegion)
|
||
|
continue;
|
||
|
|
||
|
markPtrSymbolsReleased(Call, State, ArgRegion, C);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// [string.require]
|
||
|
//
|
||
|
// "References, pointers, and iterators referring to the elements of a
|
||
|
// basic_string sequence may be invalidated by the following uses of that
|
||
|
// basic_string object:
|
||
|
//
|
||
|
// -- As an argument to any standard library function taking a reference
|
||
|
// to non-const basic_string as an argument. For example, as an argument to
|
||
|
// non-member functions swap(), operator>>(), and getline(), or as an argument
|
||
|
// to basic_string::swap().
|
||
|
//
|
||
|
// -- Calling non-const member functions, except operator[], at, front, back,
|
||
|
// begin, rbegin, end, and rend."
|
||
|
|
||
|
void InnerPointerChecker::checkPostCall(const CallEvent &Call,
|
||
|
CheckerContext &C) const {
|
||
|
ProgramStateRef State = C.getState();
|
||
|
|
||
|
if (const auto *ICall = dyn_cast<CXXInstanceCall>(&Call)) {
|
||
|
// TODO: Do we need these to be typed?
|
||
|
const auto *ObjRegion = dyn_cast_or_null<TypedValueRegion>(
|
||
|
ICall->getCXXThisVal().getAsRegion());
|
||
|
if (!ObjRegion)
|
||
|
return;
|
||
|
|
||
|
if (Call.isCalled(CStrFn) || Call.isCalled(DataFn)) {
|
||
|
SVal RawPtr = Call.getReturnValue();
|
||
|
if (SymbolRef Sym = RawPtr.getAsSymbol(/*IncludeBaseRegions=*/true)) {
|
||
|
// Start tracking this raw pointer by adding it to the set of symbols
|
||
|
// associated with this container object in the program state map.
|
||
|
|
||
|
PtrSet::Factory &F = State->getStateManager().get_context<PtrSet>();
|
||
|
const PtrSet *SetPtr = State->get<RawPtrMap>(ObjRegion);
|
||
|
PtrSet Set = SetPtr ? *SetPtr : F.getEmptySet();
|
||
|
assert(C.wasInlined || !Set.contains(Sym));
|
||
|
Set = F.add(Set, Sym);
|
||
|
|
||
|
State = State->set<RawPtrMap>(ObjRegion, Set);
|
||
|
C.addTransition(State);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Check [string.require] / second point.
|
||
|
if (isInvalidatingMemberFunction(Call)) {
|
||
|
markPtrSymbolsReleased(Call, State, ObjRegion, C);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Check [string.require] / first point.
|
||
|
checkFunctionArguments(Call, State, C);
|
||
|
}
|
||
|
|
||
|
void InnerPointerChecker::checkDeadSymbols(SymbolReaper &SymReaper,
|
||
|
CheckerContext &C) const {
|
||
|
ProgramStateRef State = C.getState();
|
||
|
PtrSet::Factory &F = State->getStateManager().get_context<PtrSet>();
|
||
|
RawPtrMapTy RPM = State->get<RawPtrMap>();
|
||
|
for (const auto &Entry : RPM) {
|
||
|
if (!SymReaper.isLiveRegion(Entry.first)) {
|
||
|
// Due to incomplete destructor support, some dead regions might
|
||
|
// remain in the program state map. Clean them up.
|
||
|
State = State->remove<RawPtrMap>(Entry.first);
|
||
|
}
|
||
|
if (const PtrSet *OldSet = State->get<RawPtrMap>(Entry.first)) {
|
||
|
PtrSet CleanedUpSet = *OldSet;
|
||
|
for (const auto Symbol : Entry.second) {
|
||
|
if (!SymReaper.isLive(Symbol))
|
||
|
CleanedUpSet = F.remove(CleanedUpSet, Symbol);
|
||
|
}
|
||
|
State = CleanedUpSet.isEmpty()
|
||
|
? State->remove<RawPtrMap>(Entry.first)
|
||
|
: State->set<RawPtrMap>(Entry.first, CleanedUpSet);
|
||
|
}
|
||
|
}
|
||
|
C.addTransition(State);
|
||
|
}
|
||
|
|
||
|
namespace clang {
|
||
|
namespace ento {
|
||
|
namespace allocation_state {
|
||
|
|
||
|
std::unique_ptr<BugReporterVisitor> getInnerPointerBRVisitor(SymbolRef Sym) {
|
||
|
return std::make_unique<InnerPointerChecker::InnerPointerBRVisitor>(Sym);
|
||
|
}
|
||
|
|
||
|
const MemRegion *getContainerObjRegion(ProgramStateRef State, SymbolRef Sym) {
|
||
|
RawPtrMapTy Map = State->get<RawPtrMap>();
|
||
|
for (const auto &Entry : Map) {
|
||
|
if (Entry.second.contains(Sym)) {
|
||
|
return Entry.first;
|
||
|
}
|
||
|
}
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
} // end namespace allocation_state
|
||
|
} // end namespace ento
|
||
|
} // end namespace clang
|
||
|
|
||
|
PathDiagnosticPieceRef InnerPointerChecker::InnerPointerBRVisitor::VisitNode(
|
||
|
const ExplodedNode *N, BugReporterContext &BRC, PathSensitiveBugReport &) {
|
||
|
if (!isSymbolTracked(N->getState(), PtrToBuf) ||
|
||
|
isSymbolTracked(N->getFirstPred()->getState(), PtrToBuf))
|
||
|
return nullptr;
|
||
|
|
||
|
const Stmt *S = N->getStmtForDiagnostics();
|
||
|
if (!S)
|
||
|
return nullptr;
|
||
|
|
||
|
const MemRegion *ObjRegion =
|
||
|
allocation_state::getContainerObjRegion(N->getState(), PtrToBuf);
|
||
|
const auto *TypedRegion = cast<TypedValueRegion>(ObjRegion);
|
||
|
QualType ObjTy = TypedRegion->getValueType();
|
||
|
|
||
|
SmallString<256> Buf;
|
||
|
llvm::raw_svector_ostream OS(Buf);
|
||
|
OS << "Pointer to inner buffer of '" << ObjTy.getAsString()
|
||
|
<< "' obtained here";
|
||
|
PathDiagnosticLocation Pos(S, BRC.getSourceManager(),
|
||
|
N->getLocationContext());
|
||
|
return std::make_shared<PathDiagnosticEventPiece>(Pos, OS.str(), true);
|
||
|
}
|
||
|
|
||
|
void ento::registerInnerPointerChecker(CheckerManager &Mgr) {
|
||
|
registerInnerPointerCheckerAux(Mgr);
|
||
|
Mgr.registerChecker<InnerPointerChecker>();
|
||
|
}
|
||
|
|
||
|
bool ento::shouldRegisterInnerPointerChecker(const CheckerManager &mgr) {
|
||
|
return true;
|
||
|
}
|