103#include "llvm/ADT/StringExtras.h"
106using namespace clang;
111static const StringRef HandleTypeName =
"zx_handle_t";
112static const StringRef ErrorTypeName =
"zx_status_t";
116 enum class Kind { MaybeAllocated, Allocated, Released, Escaped, Unowned } K;
118 HandleState(Kind K,
SymbolRef ErrorSym) : K(K), ErrorSym(ErrorSym) {}
122 return K ==
Other.K && ErrorSym ==
Other.ErrorSym;
124 bool isAllocated()
const {
return K == Kind::Allocated; }
125 bool maybeAllocated()
const {
return K == Kind::MaybeAllocated; }
126 bool isReleased()
const {
return K == Kind::Released; }
127 bool isEscaped()
const {
return K == Kind::Escaped; }
128 bool isUnowned()
const {
return K == Kind::Unowned; }
130 static HandleState getMaybeAllocated(
SymbolRef ErrorSym) {
131 return HandleState(Kind::MaybeAllocated, ErrorSym);
133 static HandleState getAllocated(
ProgramStateRef State, HandleState S) {
134 assert(S.maybeAllocated());
135 assert(State->getConstraintManager()
136 .isNull(State, S.getErrorSym())
138 return HandleState(Kind::Allocated,
nullptr);
140 static HandleState getReleased() {
141 return HandleState(Kind::Released,
nullptr);
143 static HandleState getEscaped() {
144 return HandleState(Kind::Escaped,
nullptr);
146 static HandleState getUnowned() {
147 return HandleState(Kind::Unowned,
nullptr);
150 SymbolRef getErrorSym()
const {
return ErrorSym; }
152 void Profile(llvm::FoldingSetNodeID &ID)
const {
153 ID.AddInteger(
static_cast<int>(K));
154 ID.AddPointer(ErrorSym);
157 LLVM_DUMP_METHOD
void dump(raw_ostream &OS)
const {
163 CASE(Kind::MaybeAllocated)
164 CASE(Kind::Allocated)
175 LLVM_DUMP_METHOD
void dump()
const {
dump(llvm::errs()); }
178template <
typename Attr>
static bool hasFuchsiaAttr(
const Decl *
D) {
182template <
typename Attr>
static bool hasFuchsiaUnownedAttr(
const Decl *
D) {
184 D->
getAttr<
Attr>()->getHandleType() ==
"FuchsiaUnowned";
187class FuchsiaHandleChecker
188 :
public Checker<check::PostCall, check::PreCall, check::DeadSymbols,
189 check::PointerEscape, eval::Assume> {
190 BugType LeakBugType{
this,
"Fuchsia handle leak",
"Fuchsia Handle Error",
192 BugType DoubleReleaseBugType{
this,
"Fuchsia handle double release",
193 "Fuchsia Handle Error"};
194 BugType UseAfterReleaseBugType{
this,
"Fuchsia handle use after release",
195 "Fuchsia Handle Error"};
197 this,
"Fuchsia handle release of unowned handle",
"Fuchsia Handle Error"};
204 bool Assumption)
const;
224 StringRef Msg)
const;
227 const char *Sep)
const override;
238 if (!State->get<HStateMap>(Sym))
239 N = N->getFirstPred();
243 State = N->getState();
244 if (!State->get<HStateMap>(Sym)) {
245 const HandleState *HState = Pred->
getState()->get<HStateMap>(Sym);
246 if (HState && (HState->isAllocated() || HState->maybeAllocated()))
256class FuchsiaHandleSymbolVisitor final :
public SymbolVisitor {
259 if (
const auto *HandleType = S->getType()->getAs<
TypedefType>())
260 if (HandleType->getDecl()->getName() == HandleTypeName)
261 Symbols.push_back(S);
276 int PtrToHandleLevel = 0;
284 FuchsiaHandleSymbolVisitor Visitor;
285 State->scanReachableSymbols(Arg, Visitor);
286 return Visitor.GetSymbols();
289 if (HandleType->getDecl()->getName() != HandleTypeName)
291 if (PtrToHandleLevel > 1)
295 if (PtrToHandleLevel == 0) {
303 assert(PtrToHandleLevel == 1);
304 if (std::optional<Loc> ArgLoc = Arg.
getAs<
Loc>()) {
305 SymbolRef Sym = State->getSVal(*ArgLoc).getAsSymbol();
317void FuchsiaHandleChecker::checkPreCall(
const CallEvent &
Call,
320 const FunctionDecl *FuncDecl = dyn_cast_or_null<FunctionDecl>(
Call.getDecl());
324 for (
unsigned Arg = 0; Arg <
Call.getNumArgs(); ++Arg) {
326 State = State->set<HStateMap>(Handle, HandleState::getEscaped());
328 C.addTransition(State);
332 for (
unsigned Arg = 0; Arg <
Call.getNumArgs(); ++Arg) {
340 if (hasFuchsiaAttr<ReleaseHandleAttr>(PVD) ||
341 hasFuchsiaAttr<AcquireHandleAttr>(PVD))
345 const HandleState *HState = State->get<HStateMap>(Handle);
346 if (!HState || HState->isEscaped())
349 if (hasFuchsiaAttr<UseHandleAttr>(PVD) ||
351 if (HState->isReleased()) {
352 reportUseAfterFree(Handle,
Call.getArgSourceRange(Arg),
C);
358 C.addTransition(State);
361void FuchsiaHandleChecker::checkPostCall(
const CallEvent &
Call,
363 const FunctionDecl *FuncDecl = dyn_cast_or_null<FunctionDecl>(
Call.getDecl());
373 std::vector<std::function<std::string(
BugReport & BR)>> Notes;
376 if (TypeDefTy->getDecl()->getName() == ErrorTypeName)
377 ResultSymbol =
Call.getReturnValue().getAsSymbol();
380 if (hasFuchsiaAttr<AcquireHandleAttr>(FuncDecl)) {
382 Notes.push_back([RetSym, FuncDecl](
BugReport &BR) -> std::string {
384 if (PathBR->getInterestingnessKind(RetSym)) {
386 llvm::raw_string_ostream OS(SBuf);
388 <<
"' returns an open handle";
394 State->set<HStateMap>(RetSym, HandleState::getMaybeAllocated(
nullptr));
395 }
else if (hasFuchsiaUnownedAttr<AcquireHandleAttr>(FuncDecl)) {
398 Notes.push_back([RetSym, FuncDecl](
BugReport &BR) -> std::string {
400 if (PathBR->getInterestingnessKind(RetSym)) {
402 llvm::raw_string_ostream OS(SBuf);
404 <<
"' returns an unowned handle";
409 State = State->set<HStateMap>(RetSym, HandleState::getUnowned());
412 for (
unsigned Arg = 0; Arg <
Call.getNumArgs(); ++Arg) {
421 const HandleState *HState = State->get<HStateMap>(Handle);
422 if (HState && HState->isEscaped())
424 if (hasFuchsiaAttr<ReleaseHandleAttr>(PVD)) {
425 if (HState && HState->isReleased()) {
426 reportDoubleRelease(Handle,
Call.getArgSourceRange(Arg),
C);
428 }
else if (HState && HState->isUnowned()) {
429 reportUnownedRelease(Handle,
Call.getArgSourceRange(Arg),
C);
432 Notes.push_back([Handle, ParamDiagIdx](
BugReport &BR) -> std::string {
434 if (PathBR->getInterestingnessKind(Handle)) {
436 llvm::raw_string_ostream OS(SBuf);
437 OS <<
"Handle released through " << ParamDiagIdx
438 << llvm::getOrdinalSuffix(ParamDiagIdx) <<
" parameter";
443 State = State->set<HStateMap>(Handle, HandleState::getReleased());
445 }
else if (hasFuchsiaAttr<AcquireHandleAttr>(PVD)) {
446 Notes.push_back([Handle, ParamDiagIdx](
BugReport &BR) -> std::string {
448 if (PathBR->getInterestingnessKind(Handle)) {
450 llvm::raw_string_ostream OS(SBuf);
451 OS <<
"Handle allocated through " << ParamDiagIdx
452 << llvm::getOrdinalSuffix(ParamDiagIdx) <<
" parameter";
457 State = State->set<HStateMap>(
458 Handle, HandleState::getMaybeAllocated(ResultSymbol));
459 }
else if (hasFuchsiaUnownedAttr<AcquireHandleAttr>(PVD)) {
460 Notes.push_back([Handle, ParamDiagIdx](
BugReport &BR) -> std::string {
462 if (PathBR->getInterestingnessKind(Handle)) {
464 llvm::raw_string_ostream OS(SBuf);
465 OS <<
"Unowned handle allocated through " << ParamDiagIdx
466 << llvm::getOrdinalSuffix(ParamDiagIdx) <<
" parameter";
471 State = State->set<HStateMap>(Handle, HandleState::getUnowned());
472 }
else if (!hasFuchsiaAttr<UseHandleAttr>(PVD) &&
479 State = State->set<HStateMap>(Handle, HandleState::getEscaped());
484 if (!Notes.empty()) {
485 T =
C.getNoteTag([
this, Notes{std::move(Notes)}](
487 if (&BR.
getBugType() != &UseAfterReleaseBugType &&
492 for (
auto &
Note : Notes) {
500 C.addTransition(State,
T);
503void FuchsiaHandleChecker::checkDeadSymbols(
SymbolReaper &SymReaper,
507 HStateMapTy TrackedHandles = State->get<HStateMap>();
508 for (
auto &CurItem : TrackedHandles) {
509 SymbolRef ErrorSym = CurItem.second.getErrorSym();
514 if (!SymReaper.
isDead(CurItem.first) ||
515 (ErrorSym && !SymReaper.
isDead(ErrorSym)))
517 if (CurItem.second.isAllocated() || CurItem.second.maybeAllocated())
518 LeakedSyms.push_back(CurItem.first);
519 State = State->remove<HStateMap>(CurItem.first);
523 if (!LeakedSyms.empty())
524 N = reportLeaks(LeakedSyms,
C, N);
526 C.addTransition(State, N);
541 bool Assumption)
const {
544 HStateMapTy TrackedHandles = State->get<HStateMap>();
545 for (
auto &CurItem : TrackedHandles) {
549 State = State->remove<HStateMap>(CurItem.first);
551 SymbolRef ErrorSym = CurItem.second.getErrorSym();
557 if (CurItem.second.maybeAllocated())
558 State = State->set<HStateMap>(
559 CurItem.first, HandleState::getAllocated(State, CurItem.second));
562 if (CurItem.second.maybeAllocated())
563 State = State->remove<HStateMap>(CurItem.first);
573 Call ? dyn_cast_or_null<FunctionDecl>(
Call->getDecl()) : nullptr;
575 llvm::DenseSet<SymbolRef> UnEscaped;
580 for (
unsigned Arg = 0; Arg <
Call->getNumArgs(); ++Arg) {
587 if (hasFuchsiaAttr<UseHandleAttr>(PVD) ||
588 hasFuchsiaAttr<ReleaseHandleAttr>(PVD)) {
589 UnEscaped.insert(Handle);
597 for (
auto I : State->get<HStateMap>()) {
598 if (Escaped.count(I.first) && !UnEscaped.count(I.first))
599 State = State->set<HStateMap>(I.first, HandleState::getEscaped());
600 if (
const auto *SD = dyn_cast<SymbolDerived>(I.first)) {
601 auto ParentSym = SD->getParentSymbol();
602 if (Escaped.count(ParentSym))
603 State = State->set<HStateMap>(I.first, HandleState::getEscaped());
613 ExplodedNode *ErrNode =
C.generateNonFatalErrorNode(
C.getState(), Pred);
614 for (
SymbolRef LeakedHandle : LeakedHandles) {
615 reportBug(LeakedHandle, ErrNode,
C,
nullptr, LeakBugType,
616 "Potential leak of handle");
621void FuchsiaHandleChecker::reportDoubleRelease(
SymbolRef HandleSym,
625 reportBug(HandleSym, ErrNode,
C, &
Range, DoubleReleaseBugType,
626 "Releasing a previously released handle");
629void FuchsiaHandleChecker::reportUnownedRelease(
SymbolRef HandleSym,
633 reportBug(HandleSym, ErrNode,
C, &
Range, ReleaseUnownedBugType,
634 "Releasing an unowned handle");
637void FuchsiaHandleChecker::reportUseAfterFree(
SymbolRef HandleSym,
641 reportBug(HandleSym, ErrNode,
C, &
Range, UseAfterReleaseBugType,
642 "Using a previously released handle");
652 std::unique_ptr<PathSensitiveBugReport> R;
653 if (
Type.isSuppressOnSink()) {
657 assert(S &&
"Statement cannot be null.");
662 R = std::make_unique<PathSensitiveBugReport>(
663 Type, Msg, ErrorNode, LocUsedForUniqueing,
668 R = std::make_unique<PathSensitiveBugReport>(
Type, Msg, ErrorNode);
671 R->markInteresting(Sym);
672 C.emitReport(std::move(R));
679bool ento::shouldRegisterFuchsiaHandleChecker(
const CheckerManager &mgr) {
683void FuchsiaHandleChecker::printState(raw_ostream &Out,
ProgramStateRef State,
684 const char *NL,
const char *Sep)
const {
686 HStateMapTy StateMap = State->get<HStateMap>();
688 if (!StateMap.isEmpty()) {
689 Out << Sep <<
"FuchsiaHandleChecker :" << NL;
690 for (
const auto &[Sym, HandleState] : StateMap) {
693 HandleState.dump(Out);
static void dump(llvm::raw_ostream &OS, StringRef FunctionName, ArrayRef< CounterExpression > Expressions, ArrayRef< CounterMappingRegion > Regions)
static const ExplodedNode * getAcquireSite(const ExplodedNode *N, SymbolRef Sym, CheckerContext &Ctx)
static SmallVector< SymbolRef, 1024 > getFuchsiaHandleSymbols(QualType QT, SVal Arg, ProgramStateRef State)
Returns the symbols extracted from the argument or empty vector if it cannot be found.
static bool isReleased(SymbolRef Sym, CheckerContext &C)
Check if the memory associated with this symbol was released.
#define REGISTER_MAP_WITH_PROGRAMSTATE(Name, Key, Value)
Declares an immutable map of type NameTy, suitable for placement into the ProgramState.
C Language Family Type Representation.
Attr - This represents one attribute.
Decl - This represents one declaration (or definition), e.g.
Represents a function declaration or definition.
const ParmVarDecl * getParamDecl(unsigned i) const
QualType getReturnType() const
unsigned getNumParams() const
Return the number of parameters this function must have based on its FunctionType.
const Decl * getDecl() const
DeclarationName getDeclName() const
Get the actual, stored name of the declaration, which may be a special name.
Represents a parameter to a function.
unsigned getFunctionScopeIndex() const
Returns the index of this parameter in its prototype or method scope.
A (possibly-)qualified type.
A trivial tuple used to represent a source range.
Stmt - This represents one statement.
The base class of the type hierarchy.
bool isStructureType() const
bool isIntegerType() const
isIntegerType() does not include complex integers (a GCC extension).
bool isReferenceType() const
QualType getPointeeType() const
If this is a pointer, ObjC object pointer, or block pointer, this returns the respective pointee.
bool isAnyPointerType() const
const T * getAs() const
Member-template getAs<specific type>'.
This class provides an interface through which checkers can create individual bug reports.
const BugType & getBugType() const
Represents an abstract call to a function or method along a particular path.
virtual void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, const char *Sep) const
See CheckerManager::runCheckersForPrintState.
CHECKER * registerChecker(AT &&... Args)
Used to register checkers.
bool isConstrainedFalse() const
Return true if the constraint is perfectly constrained to 'false'.
bool isConstrainedTrue() const
Return true if the constraint is perfectly constrained to 'true'.
ConditionTruthVal isNull(ProgramStateRef State, SymbolRef Sym)
Convenience method to query the state to see if a symbol is null or not null, or if neither assumptio...
const ProgramStateRef & getState() const
const Stmt * getStmtForDiagnostics() const
If the node's program point corresponds to a statement, retrieve that statement.
const LocationContext * getLocationContext() const
ExplodedNode * getFirstPred()
The tag upon which the TagVisitor reacts.
static PathDiagnosticLocation createBegin(const Decl *D, const SourceManager &SM)
Create a location for the beginning of the declaration.
A Range represents the closed range [from, to].
SVal - This represents a symbolic expression, which can be either an L-value or an R-value.
SymbolRef getAsSymbol(bool IncludeBaseRegions=false) const
If this SVal wraps a symbol return that SymbolRef.
std::optional< T > getAs() const
Convert to the specified SVal type, returning std::nullopt if this SVal is not of the desired type.
virtual void dumpToStream(raw_ostream &os) const
A class responsible for cleaning up unused symbols.
bool isDead(SymbolRef sym)
Returns whether or not a symbol has been confirmed dead.
virtual bool VisitSymbol(SymbolRef sym)=0
A visitor method invoked by ProgramStateManager::scanReachableSymbols.
PointerEscapeKind
Describes the different reasons a pointer escapes during analysis.
@ PSK_DirectEscapeOnCall
The pointer has been passed to a function call directly.
@ PSK_IndirectEscapeOnCall
The pointer has been passed to a function indirectly.
@ PSK_EscapeOutParameters
Escape for a new symbol that was generated into a region that the analyzer cannot follow during a con...
llvm::DenseSet< SymbolRef > InvalidatedSymbols
The JSON file list parser is used to communicate input to InstallAPI.
bool operator==(const CallGraphNode::CallRecord &LHS, const CallGraphNode::CallRecord &RHS)
const FunctionProtoType * T
@ Other
Other implicit parameter.