#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
#include "clang/AST/Type.h"
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ConstraintManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h"
#include "llvm/ADT/StringExtras.h"
using namespace clang;
using namespace ento;
namespace {
static const StringRef HandleTypeName = "zx_handle_t";
static const StringRef ErrorTypeName = "zx_status_t";
class HandleState {
private:
enum class Kind { MaybeAllocated, Allocated, Released, Escaped, Unowned } K;
SymbolRef ErrorSym;
HandleState(Kind K, SymbolRef ErrorSym) : K(K), ErrorSym(ErrorSym) {}
public:
bool operator==(const HandleState &Other) const {
return K == Other.K && ErrorSym == Other.ErrorSym;
}
bool isAllocated() const { return K == Kind::Allocated; }
bool maybeAllocated() const { return K == Kind::MaybeAllocated; }
bool isReleased() const { return K == Kind::Released; }
bool isEscaped() const { return K == Kind::Escaped; }
bool isUnowned() const { return K == Kind::Unowned; }
static HandleState getMaybeAllocated(SymbolRef ErrorSym) {
return HandleState(Kind::MaybeAllocated, ErrorSym);
}
static HandleState getAllocated(ProgramStateRef State, HandleState S) {
assert(S.maybeAllocated());
assert(State->getConstraintManager()
.isNull(State, S.getErrorSym())
.isConstrained());
return HandleState(Kind::Allocated, nullptr);
}
static HandleState getReleased() {
return HandleState(Kind::Released, nullptr);
}
static HandleState getEscaped() {
return HandleState(Kind::Escaped, nullptr);
}
static HandleState getUnowned() {
return HandleState(Kind::Unowned, nullptr);
}
SymbolRef getErrorSym() const { return ErrorSym; }
void Profile(llvm::FoldingSetNodeID &ID) const {
ID.AddInteger(static_cast<int>(K));
ID.AddPointer(ErrorSym);
}
LLVM_DUMP_METHOD void dump(raw_ostream &OS) const {
switch (K) {
#define CASE(ID) \
case ID: \
OS << #ID; \
break;
CASE(Kind::MaybeAllocated)
CASE(Kind::Allocated)
CASE(Kind::Released)
CASE(Kind::Escaped)
CASE(Kind::Unowned)
}
if (ErrorSym) {
OS << " ErrorSym: ";
ErrorSym->dumpToStream(OS);
}
}
LLVM_DUMP_METHOD void dump() const { dump(llvm::errs()); }
};
template <typename Attr> static bool hasFuchsiaAttr(const Decl *D) {
return D->hasAttr<Attr>() && D->getAttr<Attr>()->getHandleType() == "Fuchsia";
}
template <typename Attr> static bool hasFuchsiaUnownedAttr(const Decl *D) {
return D->hasAttr<Attr>() &&
D->getAttr<Attr>()->getHandleType() == "FuchsiaUnowned";
}
class FuchsiaHandleChecker
: public Checker<check::PostCall, check::PreCall, check::DeadSymbols,
check::PointerEscape, eval::Assume> {
BugType LeakBugType{this, "Fuchsia handle leak", "Fuchsia Handle Error",
true};
BugType DoubleReleaseBugType{this, "Fuchsia handle double release",
"Fuchsia Handle Error"};
BugType UseAfterReleaseBugType{this, "Fuchsia handle use after release",
"Fuchsia Handle Error"};
BugType ReleaseUnownedBugType{
this, "Fuchsia handle release of unowned handle", "Fuchsia Handle Error"};
public:
void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
ProgramStateRef evalAssume(ProgramStateRef State, SVal Cond,
bool Assumption) const;
ProgramStateRef checkPointerEscape(ProgramStateRef State,
const InvalidatedSymbols &Escaped,
const CallEvent *Call,
PointerEscapeKind Kind) const;
ExplodedNode *reportLeaks(ArrayRef<SymbolRef> LeakedHandles,
CheckerContext &C, ExplodedNode *Pred) const;
void reportDoubleRelease(SymbolRef HandleSym, const SourceRange &Range,
CheckerContext &C) const;
void reportUnownedRelease(SymbolRef HandleSym, const SourceRange &Range,
CheckerContext &C) const;
void reportUseAfterFree(SymbolRef HandleSym, const SourceRange &Range,
CheckerContext &C) const;
void reportBug(SymbolRef Sym, ExplodedNode *ErrorNode, CheckerContext &C,
const SourceRange *Range, const BugType &Type,
StringRef Msg) const;
void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
const char *Sep) const override;
};
}
REGISTER_MAP_WITH_PROGRAMSTATE(HStateMap, SymbolRef, HandleState)
static const ExplodedNode *getAcquireSite(const ExplodedNode *N, SymbolRef Sym,
CheckerContext &Ctx) {
ProgramStateRef State = N->getState();
if (!State->get<HStateMap>(Sym))
N = N->getFirstPred();
const ExplodedNode *Pred = N;
while (N) {
State = N->getState();
if (!State->get<HStateMap>(Sym)) {
const HandleState *HState = Pred->getState()->get<HStateMap>(Sym);
if (HState && (HState->isAllocated() || HState->maybeAllocated()))
return N;
}
Pred = N;
N = N->getFirstPred();
}
return nullptr;
}
namespace {
class FuchsiaHandleSymbolVisitor final : public SymbolVisitor {
public:
bool VisitSymbol(SymbolRef S) override {
if (const auto *HandleType = S->getType()->getAs<TypedefType>())
if (HandleType->getDecl()->getName() == HandleTypeName)
Symbols.push_back(S);
return true;
}
SmallVector<SymbolRef, 1024> GetSymbols() { return Symbols; }
private:
SmallVector<SymbolRef, 1024> Symbols;
};
}
static SmallVector<SymbolRef, 1024>
getFuchsiaHandleSymbols(QualType QT, SVal Arg, ProgramStateRef State) {
int PtrToHandleLevel = 0;
while (QT->isAnyPointerType() || QT->isReferenceType()) {
++PtrToHandleLevel;
QT = QT->getPointeeType();
}
if (QT->isStructureType()) {
FuchsiaHandleSymbolVisitor Visitor;
State->scanReachableSymbols(Arg, Visitor);
return Visitor.GetSymbols();
}
if (const auto *HandleType = QT->getAs<TypedefType>()) {
if (HandleType->getDecl()->getName() != HandleTypeName)
return {};
if (PtrToHandleLevel > 1)
return {};
if (PtrToHandleLevel == 0) {
SymbolRef Sym = Arg.getAsSymbol();
if (Sym) {
return {Sym};
} else {
return {};
}
} else {
assert(PtrToHandleLevel == 1);
if (Optional<Loc> ArgLoc = Arg.getAs<Loc>()) {
SymbolRef Sym = State->getSVal(*ArgLoc).getAsSymbol();
if (Sym) {
return {Sym};
} else {
return {};
}
}
}
}
return {};
}
void FuchsiaHandleChecker::checkPreCall(const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
const FunctionDecl *FuncDecl = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
if (!FuncDecl) {
for (unsigned Arg = 0; Arg < Call.getNumArgs(); ++Arg) {
if (SymbolRef Handle = Call.getArgSVal(Arg).getAsSymbol())
State = State->set<HStateMap>(Handle, HandleState::getEscaped());
}
C.addTransition(State);
return;
}
for (unsigned Arg = 0; Arg < Call.getNumArgs(); ++Arg) {
if (Arg >= FuncDecl->getNumParams())
break;
const ParmVarDecl *PVD = FuncDecl->getParamDecl(Arg);
SmallVector<SymbolRef, 1024> Handles =
getFuchsiaHandleSymbols(PVD->getType(), Call.getArgSVal(Arg), State);
if (hasFuchsiaAttr<ReleaseHandleAttr>(PVD) ||
hasFuchsiaAttr<AcquireHandleAttr>(PVD))
continue;
for (SymbolRef Handle : Handles) {
const HandleState *HState = State->get<HStateMap>(Handle);
if (!HState || HState->isEscaped())
continue;
if (hasFuchsiaAttr<UseHandleAttr>(PVD) ||
PVD->getType()->isIntegerType()) {
if (HState->isReleased()) {
reportUseAfterFree(Handle, Call.getArgSourceRange(Arg), C);
return;
}
}
}
}
C.addTransition(State);
}
void FuchsiaHandleChecker::checkPostCall(const CallEvent &Call,
CheckerContext &C) const {
const FunctionDecl *FuncDecl = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
if (!FuncDecl)
return;
if (C.wasInlined)
return;
ProgramStateRef State = C.getState();
std::vector<std::function<std::string(BugReport & BR)>> Notes;
SymbolRef ResultSymbol = nullptr;
if (const auto *TypeDefTy = FuncDecl->getReturnType()->getAs<TypedefType>())
if (TypeDefTy->getDecl()->getName() == ErrorTypeName)
ResultSymbol = Call.getReturnValue().getAsSymbol();
if (hasFuchsiaAttr<AcquireHandleAttr>(FuncDecl)) {
SymbolRef RetSym = Call.getReturnValue().getAsSymbol();
Notes.push_back([RetSym, FuncDecl](BugReport &BR) -> std::string {
auto *PathBR = static_cast<PathSensitiveBugReport *>(&BR);
if (auto IsInteresting = PathBR->getInterestingnessKind(RetSym)) {
std::string SBuf;
llvm::raw_string_ostream OS(SBuf);
OS << "Function '" << FuncDecl->getDeclName()
<< "' returns an open handle";
return SBuf;
} else
return "";
});
State =
State->set<HStateMap>(RetSym, HandleState::getMaybeAllocated(nullptr));
} else if (hasFuchsiaUnownedAttr<AcquireHandleAttr>(FuncDecl)) {
SymbolRef RetSym = Call.getReturnValue().getAsSymbol();
Notes.push_back([RetSym, FuncDecl](BugReport &BR) -> std::string {
auto *PathBR = static_cast<PathSensitiveBugReport *>(&BR);
if (auto IsInteresting = PathBR->getInterestingnessKind(RetSym)) {
std::string SBuf;
llvm::raw_string_ostream OS(SBuf);
OS << "Function '" << FuncDecl->getDeclName()
<< "' returns an unowned handle";
return SBuf;
} else
return "";
});
State = State->set<HStateMap>(RetSym, HandleState::getUnowned());
}
for (unsigned Arg = 0; Arg < Call.getNumArgs(); ++Arg) {
if (Arg >= FuncDecl->getNumParams())
break;
const ParmVarDecl *PVD = FuncDecl->getParamDecl(Arg);
unsigned ParamDiagIdx = PVD->getFunctionScopeIndex() + 1;
SmallVector<SymbolRef, 1024> Handles =
getFuchsiaHandleSymbols(PVD->getType(), Call.getArgSVal(Arg), State);
for (SymbolRef Handle : Handles) {
const HandleState *HState = State->get<HStateMap>(Handle);
if (HState && HState->isEscaped())
continue;
if (hasFuchsiaAttr<ReleaseHandleAttr>(PVD)) {
if (HState && HState->isReleased()) {
reportDoubleRelease(Handle, Call.getArgSourceRange(Arg), C);
return;
} else if (HState && HState->isUnowned()) {
reportUnownedRelease(Handle, Call.getArgSourceRange(Arg), C);
return;
} else {
Notes.push_back([Handle, ParamDiagIdx](BugReport &BR) -> std::string {
auto *PathBR = static_cast<PathSensitiveBugReport *>(&BR);
if (auto IsInteresting = PathBR->getInterestingnessKind(Handle)) {
std::string SBuf;
llvm::raw_string_ostream OS(SBuf);
OS << "Handle released through " << ParamDiagIdx
<< llvm::getOrdinalSuffix(ParamDiagIdx) << " parameter";
return SBuf;
} else
return "";
});
State = State->set<HStateMap>(Handle, HandleState::getReleased());
}
} else if (hasFuchsiaAttr<AcquireHandleAttr>(PVD)) {
Notes.push_back([Handle, ParamDiagIdx](BugReport &BR) -> std::string {
auto *PathBR = static_cast<PathSensitiveBugReport *>(&BR);
if (auto IsInteresting = PathBR->getInterestingnessKind(Handle)) {
std::string SBuf;
llvm::raw_string_ostream OS(SBuf);
OS << "Handle allocated through " << ParamDiagIdx
<< llvm::getOrdinalSuffix(ParamDiagIdx) << " parameter";
return SBuf;
} else
return "";
});
State = State->set<HStateMap>(
Handle, HandleState::getMaybeAllocated(ResultSymbol));
} else if (hasFuchsiaUnownedAttr<AcquireHandleAttr>(PVD)) {
Notes.push_back([Handle, ParamDiagIdx](BugReport &BR) -> std::string {
auto *PathBR = static_cast<PathSensitiveBugReport *>(&BR);
if (auto IsInteresting = PathBR->getInterestingnessKind(Handle)) {
std::string SBuf;
llvm::raw_string_ostream OS(SBuf);
OS << "Unowned handle allocated through " << ParamDiagIdx
<< llvm::getOrdinalSuffix(ParamDiagIdx) << " parameter";
return SBuf;
} else
return "";
});
State = State->set<HStateMap>(Handle, HandleState::getUnowned());
} else if (!hasFuchsiaAttr<UseHandleAttr>(PVD) &&
PVD->getType()->isIntegerType()) {
State = State->set<HStateMap>(Handle, HandleState::getEscaped());
}
}
}
const NoteTag *T = nullptr;
if (!Notes.empty()) {
T = C.getNoteTag([this, Notes{std::move(Notes)}](
PathSensitiveBugReport &BR) -> std::string {
if (&BR.getBugType() != &UseAfterReleaseBugType &&
&BR.getBugType() != &LeakBugType &&
&BR.getBugType() != &DoubleReleaseBugType &&
&BR.getBugType() != &ReleaseUnownedBugType)
return "";
for (auto &Note : Notes) {
std::string Text = Note(BR);
if (!Text.empty())
return Text;
}
return "";
});
}
C.addTransition(State, T);
}
void FuchsiaHandleChecker::checkDeadSymbols(SymbolReaper &SymReaper,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
SmallVector<SymbolRef, 2> LeakedSyms;
HStateMapTy TrackedHandles = State->get<HStateMap>();
for (auto &CurItem : TrackedHandles) {
SymbolRef ErrorSym = CurItem.second.getErrorSym();
if (!SymReaper.isDead(CurItem.first) ||
(ErrorSym && !SymReaper.isDead(ErrorSym)))
continue;
if (CurItem.second.isAllocated() || CurItem.second.maybeAllocated())
LeakedSyms.push_back(CurItem.first);
State = State->remove<HStateMap>(CurItem.first);
}
ExplodedNode *N = C.getPredecessor();
if (!LeakedSyms.empty())
N = reportLeaks(LeakedSyms, C, N);
C.addTransition(State, N);
}
ProgramStateRef FuchsiaHandleChecker::evalAssume(ProgramStateRef State,
SVal Cond,
bool Assumption) const {
ConstraintManager &Cmr = State->getConstraintManager();
HStateMapTy TrackedHandles = State->get<HStateMap>();
for (auto &CurItem : TrackedHandles) {
ConditionTruthVal HandleVal = Cmr.isNull(State, CurItem.first);
if (HandleVal.isConstrainedTrue()) {
State = State->remove<HStateMap>(CurItem.first);
}
SymbolRef ErrorSym = CurItem.second.getErrorSym();
if (!ErrorSym)
continue;
ConditionTruthVal ErrorVal = Cmr.isNull(State, ErrorSym);
if (ErrorVal.isConstrainedTrue()) {
if (CurItem.second.maybeAllocated())
State = State->set<HStateMap>(
CurItem.first, HandleState::getAllocated(State, CurItem.second));
} else if (ErrorVal.isConstrainedFalse()) {
if (CurItem.second.maybeAllocated())
State = State->remove<HStateMap>(CurItem.first);
}
}
return State;
}
ProgramStateRef FuchsiaHandleChecker::checkPointerEscape(
ProgramStateRef State, const InvalidatedSymbols &Escaped,
const CallEvent *Call, PointerEscapeKind Kind) const {
const FunctionDecl *FuncDecl =
Call ? dyn_cast_or_null<FunctionDecl>(Call->getDecl()) : nullptr;
llvm::DenseSet<SymbolRef> UnEscaped;
if (FuncDecl &&
(Kind == PSK_DirectEscapeOnCall || Kind == PSK_IndirectEscapeOnCall ||
Kind == PSK_EscapeOutParameters)) {
for (unsigned Arg = 0; Arg < Call->getNumArgs(); ++Arg) {
if (Arg >= FuncDecl->getNumParams())
break;
const ParmVarDecl *PVD = FuncDecl->getParamDecl(Arg);
SmallVector<SymbolRef, 1024> Handles =
getFuchsiaHandleSymbols(PVD->getType(), Call->getArgSVal(Arg), State);
for (SymbolRef Handle : Handles) {
if (hasFuchsiaAttr<UseHandleAttr>(PVD) ||
hasFuchsiaAttr<ReleaseHandleAttr>(PVD)) {
UnEscaped.insert(Handle);
}
}
}
}
for (auto I : State->get<HStateMap>()) {
if (Escaped.count(I.first) && !UnEscaped.count(I.first))
State = State->set<HStateMap>(I.first, HandleState::getEscaped());
if (const auto *SD = dyn_cast<SymbolDerived>(I.first)) {
auto ParentSym = SD->getParentSymbol();
if (Escaped.count(ParentSym))
State = State->set<HStateMap>(I.first, HandleState::getEscaped());
}
}
return State;
}
ExplodedNode *
FuchsiaHandleChecker::reportLeaks(ArrayRef<SymbolRef> LeakedHandles,
CheckerContext &C, ExplodedNode *Pred) const {
ExplodedNode *ErrNode = C.generateNonFatalErrorNode(C.getState(), Pred);
for (SymbolRef LeakedHandle : LeakedHandles) {
reportBug(LeakedHandle, ErrNode, C, nullptr, LeakBugType,
"Potential leak of handle");
}
return ErrNode;
}
void FuchsiaHandleChecker::reportDoubleRelease(SymbolRef HandleSym,
const SourceRange &Range,
CheckerContext &C) const {
ExplodedNode *ErrNode = C.generateErrorNode(C.getState());
reportBug(HandleSym, ErrNode, C, &Range, DoubleReleaseBugType,
"Releasing a previously released handle");
}
void FuchsiaHandleChecker::reportUnownedRelease(SymbolRef HandleSym,
const SourceRange &Range,
CheckerContext &C) const {
ExplodedNode *ErrNode = C.generateErrorNode(C.getState());
reportBug(HandleSym, ErrNode, C, &Range, ReleaseUnownedBugType,
"Releasing an unowned handle");
}
void FuchsiaHandleChecker::reportUseAfterFree(SymbolRef HandleSym,
const SourceRange &Range,
CheckerContext &C) const {
ExplodedNode *ErrNode = C.generateErrorNode(C.getState());
reportBug(HandleSym, ErrNode, C, &Range, UseAfterReleaseBugType,
"Using a previously released handle");
}
void FuchsiaHandleChecker::reportBug(SymbolRef Sym, ExplodedNode *ErrorNode,
CheckerContext &C,
const SourceRange *Range,
const BugType &Type, StringRef Msg) const {
if (!ErrorNode)
return;
std::unique_ptr<PathSensitiveBugReport> R;
if (Type.isSuppressOnSink()) {
const ExplodedNode *AcquireNode = getAcquireSite(ErrorNode, Sym, C);
if (AcquireNode) {
PathDiagnosticLocation LocUsedForUniqueing =
PathDiagnosticLocation::createBegin(
AcquireNode->getStmtForDiagnostics(), C.getSourceManager(),
AcquireNode->getLocationContext());
R = std::make_unique<PathSensitiveBugReport>(
Type, Msg, ErrorNode, LocUsedForUniqueing,
AcquireNode->getLocationContext()->getDecl());
}
}
if (!R)
R = std::make_unique<PathSensitiveBugReport>(Type, Msg, ErrorNode);
if (Range)
R->addRange(*Range);
R->markInteresting(Sym);
C.emitReport(std::move(R));
}
void ento::registerFuchsiaHandleChecker(CheckerManager &mgr) {
mgr.registerChecker<FuchsiaHandleChecker>();
}
bool ento::shouldRegisterFuchsiaHandleChecker(const CheckerManager &mgr) {
return true;
}
void FuchsiaHandleChecker::printState(raw_ostream &Out, ProgramStateRef State,
const char *NL, const char *Sep) const {
HStateMapTy StateMap = State->get<HStateMap>();
if (!StateMap.isEmpty()) {
Out << Sep << "FuchsiaHandleChecker :" << NL;
for (HStateMapTy::iterator I = StateMap.begin(), E = StateMap.end(); I != E;
++I) {
I.getKey()->dumpToStream(Out);
Out << " : ";
I.getData().dump(Out);
Out << NL;
}
}
}