diff --git a/clang/docs/analyzer/checkers.rst b/clang/docs/analyzer/checkers.rst --- a/clang/docs/analyzer/checkers.rst +++ b/clang/docs/analyzer/checkers.rst @@ -1335,6 +1335,31 @@ &kCFTypeArrayCallBacks); // warn } +Fuchsia +^^^^^^^ + +Fuchsia is an open source capability-based operating system currently being +developed by Google. This section describes checkers that can find various +misuses of Fuchsia APIs. + +.. _fuchsia-HandleChecker: + +fuchsia.HandleChecker +"""""""""""""""""""""""""""" +Handles identify resources. Similar to pointers they can be leaked, +double freed, or use after freed. This check attempts to find such problems. + +.. code-block:: cpp + + void checkLeak08(int tag) { + zx_handle_t sa, sb; + zx_channel_create(0, &sa, &sb); + if (tag) + zx_handle_close(sa); + use(sb); // Warn: Potential leak of handle + zx_handle_close(sb); + } + .. _alpha-checkers: diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -108,6 +108,8 @@ def NonDeterminismAlpha : Package<"nondeterminism">, ParentPackage; +def Fuchsia : Package<"fuchsia">; + //===----------------------------------------------------------------------===// // Core Checkers. //===----------------------------------------------------------------------===// @@ -1423,3 +1425,16 @@ Documentation; } // end alpha.nondeterminism + +//===----------------------------------------------------------------------===// +// Fuchsia checkers. +//===----------------------------------------------------------------------===// + +let ParentPackage = Fuchsia in { + +def FuchsiaHandleChecker : Checker<"HandleChecker">, + HelpText<"A Checker that detect leaks related to Fuchsia handles">, + Documentation; + +} // end fuchsia + diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h @@ -213,6 +213,22 @@ return addTransition(State, (Tag ? Tag : Location.getTag())); } + /// Generate a transition to a node that will be used to report + /// an error. This node will not be a sink. That is, exploration will + /// continue along this path. + /// + /// @param State The state of the generated node. + /// @param Pred The transition will be generated from the specified Pred node + /// to the newly generated node. + /// @param Tag The tag to uniquely identify the creation site. If null, + /// the default tag for the checker will be used. + ExplodedNode * + generateNonFatalErrorNode(ProgramStateRef State, + ExplodedNode *Pred, + const ProgramPointTag *Tag = nullptr) { + return addTransition(State, Pred, (Tag ? Tag : Location.getTag())); + } + /// Emit the diagnostics report. void emitReport(std::unique_ptr R) { Changed = true; diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -2794,6 +2794,8 @@ CmdArgs.push_back( "-analyzer-checker=security.insecureAPI.decodeValueOfObjCType"); } + else if (Triple.isOSFuchsia()) + CmdArgs.push_back("-analyzer-checker=fuchsia"); CmdArgs.push_back("-analyzer-checker=deadcode"); diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -38,6 +38,7 @@ EnumCastOutOfRangeChecker.cpp ExprInspectionChecker.cpp FixedAddressChecker.cpp + FuchsiaHandleChecker.cpp GCDAntipatternChecker.cpp GenericTaintChecker.cpp GTestChecker.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/FuchsiaHandleChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/FuchsiaHandleChecker.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/FuchsiaHandleChecker.cpp @@ -0,0 +1,486 @@ +//=== FuchsiaHandleChecker.cpp - Find handle leaks/double closes -*- 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 checker checks if the handle of Fuchsia is properly used according to +// following rules. +// - If a handle is acquired, it should be released before execution +// ends. +// - If a handle is released, it should not be released again. +// - If a handle is released, it should not be used for other purposes +// such as I/O. +// +// In this checker, each tracked handle is associated with a state. When the +// handle variable is passed to different function calls or syscalls, its state +// changes. The state changes can be generally represented by following ASCII +// Art: +// +// +// +-+---------v-+ +------------+ +// acquire_func succeeded | | Escape | | +// +-----------------> Allocated +---------> Escaped <--+ +// | | | | | | +// | +-----+------++ +------------+ | +// | | | | +// | release_func | +--+ | +// | | | handle +--------+ | +// | | | dies | | | +// | +----v-----+ +---------> Leaked | | +// | | | |(REPORT)| | +// +----------+--+ | Released | Escape +--------+ | +// | | | +---------------------------+ +// | Not tracked <--+ +----+---+-+ +// | | | | | As argument by value +// +------+------+ | release_func | +------+ in function call +// | | | | or by reference in +// | | | | use_func call +// +---------+ +----v-----+ | +-----------+ +// acquire_func failed | Double | +-----> Use after | +// | released | | released | +// | (REPORT) | | (REPORT) | +// +----------+ +-----------+ +// +// acquire_func represents the functions or syscalls that may acquire a handle. +// release_func represents the functions or syscalls that may release a handle. +// use_func represents the functions or syscall that requires an open handle. +// +// If a tracked handle dies in "Released" or "Not Tracked" state, we assume it +// is properly used. Otherwise a bug and will be reported. +// +// Note that, the analyzer does not always know for sure if a function failed +// or succeeded. In those cases we use the state MaybeAllocated. +// Thus, the diagramm above captures the intent, not implementation details. +// +// Due to the fact that the number of handle related syscalls in Fuchsia +// is large, we adopt the annotation attributes to descript syscalls' +// operations(acquire/release/use) on handles instead of hardcoding +// everything in the checker. +// +// We use following annotation attributes for handle related syscalls or +// functions: +// 1. __attribute__((acquire_handle("Fuchsia"))) |handle will be acquired +// 2. __attribute__((release_handle("Fuchsia"))) |handle will be released +// 3. __attribute__((use_handle("Fuchsia"))) |handle will not transit to +// escaped state, it also needs to be open. +// +// For example, an annotated syscall: +// zx_status_t zx_channel_create( +// uint32_t options, +// zx_handle_t* out0 __attribute__((acquire_handle("Fuchsia"))) , +// zx_handle_t* out1 __attribute__((acquire_handle("Fuchsia")))); +// denotes a syscall which will acquire two handles and save them to 'out0' and +// 'out1' when succeeded. +// +//===----------------------------------------------------------------------===// + +#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" + +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 } 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; } + + 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); + } + + SymbolRef getErrorSym() const { return ErrorSym; } + + void Profile(llvm::FoldingSetNodeID &ID) const { + ID.AddInteger(static_cast(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) + } + } + + LLVM_DUMP_METHOD void dump() const { dump(llvm::errs()); } +}; + +template static bool hasFuchsiaAttr(const Decl *D) { + return D->hasAttr() && D->getAttr()->getHandleType() == "Fuchsia"; +} + +class FuchsiaHandleChecker + : public Checker { + BugType LeakBugType{this, "Fuchsia handle leak", "Fuchsia Handle Error", + /*SuppressOnSink=*/true}; + BugType DoubleReleaseBugType{this, "Fuchsia handle double release", + "Fuchsia Handle Error"}; + BugType UseAfterReleaseBugType{this, "Fuchsia handle use after release", + "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 LeakedHandles, + CheckerContext &C, ExplodedNode *Pred) const; + + void reportDoubleRelease(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; +}; +} // end anonymous namespace + +REGISTER_MAP_WITH_PROGRAMSTATE(HStateMap, SymbolRef, HandleState) + +/// Returns the symbols extracted from the argument or null if it cannot be +/// found. +SymbolRef getFuchsiaHandleSymbol(QualType QT, SVal Arg, ProgramStateRef State) { + int PtrToHandleLevel = 0; + while (QT->isAnyPointerType() || QT->isReferenceType()) { + ++PtrToHandleLevel; + QT = QT->getPointeeType(); + } + if (const auto *HandleType = QT->getAs()) { + if (HandleType->getDecl()->getName() != HandleTypeName) + return nullptr; + if (PtrToHandleLevel > 1) { + // Not supported yet. + return nullptr; + } + + if (PtrToHandleLevel == 0) { + return Arg.getAsSymbol(); + } else { + assert(PtrToHandleLevel == 1); + if (Optional ArgLoc = Arg.getAs()) + return State->getSVal(*ArgLoc).getAsSymbol(); + } + } + return nullptr; +} + +void FuchsiaHandleChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + const FunctionDecl *FuncDecl = dyn_cast_or_null(Call.getDecl()); + if (!FuncDecl) { + // Unknown call, escape by value handles. They are not covered by + // PointerEscape callback. + for (unsigned Arg = 0; Arg < Call.getNumArgs(); ++Arg) { + if (SymbolRef Handle = Call.getArgSVal(Arg).getAsSymbol()) + State = State->set(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); + SymbolRef Handle = + getFuchsiaHandleSymbol(PVD->getType(), Call.getArgSVal(Arg), State); + if (!Handle) + continue; + + // Handled in checkPostCall. + if (hasFuchsiaAttr(PVD) || + hasFuchsiaAttr(PVD)) + continue; + + const HandleState *HState = State->get(Handle); + if (!HState || HState->isEscaped()) + continue; + + if (hasFuchsiaAttr(PVD) || PVD->getType()->isIntegerType()) { + if (HState->isReleased()) { + reportUseAfterFree(Handle, Call.getArgSourceRange(Arg), C); + return; + } + } + if (!hasFuchsiaAttr(PVD) && + PVD->getType()->isIntegerType()) { + // Working around integer by-value escapes. + State = State->set(Handle, HandleState::getEscaped()); + } + } + C.addTransition(State); +} + +void FuchsiaHandleChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + const FunctionDecl *FuncDecl = dyn_cast_or_null(Call.getDecl()); + if (!FuncDecl) + return; + + ProgramStateRef State = C.getState(); + + SymbolRef ResultSymbol = nullptr; + if (const auto *TypeDefTy = FuncDecl->getReturnType()->getAs()) + if (TypeDefTy->getDecl()->getName() == ErrorTypeName) + ResultSymbol = Call.getReturnValue().getAsSymbol(); + + // Function returns an open handle. + if (hasFuchsiaAttr(FuncDecl)) { + SymbolRef RetSym = Call.getReturnValue().getAsSymbol(); + State = + State->set(RetSym, HandleState::getMaybeAllocated(nullptr)); + } + + for (unsigned Arg = 0; Arg < Call.getNumArgs(); ++Arg) { + if (Arg >= FuncDecl->getNumParams()) + break; + const ParmVarDecl *PVD = FuncDecl->getParamDecl(Arg); + SymbolRef Handle = + getFuchsiaHandleSymbol(PVD->getType(), Call.getArgSVal(Arg), State); + if (!Handle) + continue; + + const HandleState *HState = State->get(Handle); + if (HState && HState->isEscaped()) + continue; + if (hasFuchsiaAttr(PVD)) { + if (HState && HState->isReleased()) { + reportDoubleRelease(Handle, Call.getArgSourceRange(Arg), C); + return; + } else + State = State->set(Handle, HandleState::getReleased()); + } else if (hasFuchsiaAttr(PVD)) { + State = State->set( + Handle, HandleState::getMaybeAllocated(ResultSymbol)); + } + } + C.addTransition(State); +} + +void FuchsiaHandleChecker::checkDeadSymbols(SymbolReaper &SymReaper, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SmallVector LeakedSyms; + HStateMapTy TrackedHandles = State->get(); + for (auto &CurItem : TrackedHandles) { + if (!SymReaper.isDead(CurItem.first)) + continue; + if (CurItem.second.isAllocated() || CurItem.second.maybeAllocated()) + LeakedSyms.push_back(CurItem.first); + State = State->remove(CurItem.first); + } + + ExplodedNode *N = C.getPredecessor(); + if (!LeakedSyms.empty()) + N = reportLeaks(LeakedSyms, C, N); + + C.addTransition(State, N); +} + +// Acquiring a handle is not always successful. In Fuchsia most functions +// return a status code that determines the status of the handle. +// When we split the path based on this status code we know that on one +// path we do have the handle and on the other path the acquire failed. +// This method helps avoiding false positive leak warnings on paths where +// the function failed. +// Moreover, when a handle is known to be zero (the invalid handle), +// we no longer can follow the symbol on the path, becaue the constant +// zero will be used instead of the symbol. We also do not need to release +// an invalid handle, so we remove the corresponding symbol from the state. +ProgramStateRef FuchsiaHandleChecker::evalAssume(ProgramStateRef State, + SVal Cond, + bool Assumption) const { + ConstraintManager &Cmr = State->getConstraintManager(); + HStateMapTy TrackedHandles = State->get(); + for (auto &CurItem : TrackedHandles) { + ConditionTruthVal HandleVal = Cmr.isNull(State, CurItem.first); + if (HandleVal.isConstrainedTrue()) { + // The handle is invalid. We can no longer follow the symbol on this path. + State = State->remove(CurItem.first); + } + SymbolRef ErrorSym = CurItem.second.getErrorSym(); + if (!ErrorSym) + continue; + ConditionTruthVal ErrorVal = Cmr.isNull(State, ErrorSym); + if (ErrorVal.isConstrainedTrue()) { + // Allocation succeeded. + if (CurItem.second.maybeAllocated()) + State = State->set( + CurItem.first, HandleState::getAllocated(State, CurItem.second)); + } else if (ErrorVal.isConstrainedFalse()) { + // Allocation failed. + if (CurItem.second.maybeAllocated()) + State = State->remove(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(Call->getDecl()) : nullptr; + + llvm::DenseSet UnEscaped; + // Not all calls should escape our symbols. + 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); + SymbolRef Handle = + getFuchsiaHandleSymbol(PVD->getType(), Call->getArgSVal(Arg), State); + if (!Handle) + continue; + if (hasFuchsiaAttr(PVD) || + hasFuchsiaAttr(PVD)) + UnEscaped.insert(Handle); + } + } + + // For out params, we have to deal with derived symbols. See + // MacOSKeychainAPIChecker for details. + for (auto I : State->get()) { + if (Escaped.count(I.first) && !UnEscaped.count(I.first)) + State = State->set(I.first, HandleState::getEscaped()); + if (const auto *SD = dyn_cast(I.first)) { + auto ParentSym = SD->getParentSymbol(); + if (Escaped.count(ParentSym)) + State = State->set(I.first, HandleState::getEscaped()); + } + } + + return State; +} + +ExplodedNode * +FuchsiaHandleChecker::reportLeaks(ArrayRef 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::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; + + auto R = std::make_unique(Type, Msg, ErrorNode); + if (Range) + R->addRange(*Range); + R->markInteresting(Sym); + C.emitReport(std::move(R)); +} + +void ento::registerFuchsiaHandleChecker(CheckerManager &mgr) { + mgr.registerChecker(); +} + +bool ento::shouldRegisterFuchsiaHandleChecker(const LangOptions &LO) { + return true; +} + +void FuchsiaHandleChecker::printState(raw_ostream &Out, ProgramStateRef State, + const char *NL, const char *Sep) const { + + HStateMapTy StateMap = State->get(); + + 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; + } + } +} diff --git a/clang/test/Analysis/fuchsia_handle.cpp b/clang/test/Analysis/fuchsia_handle.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/fuchsia_handle.cpp @@ -0,0 +1,285 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,fuchsia.HandleChecker -verify %s + +typedef __typeof__(sizeof(int)) size_t; +typedef int zx_status_t; +typedef __typeof__(sizeof(int)) zx_handle_t; +typedef unsigned int uint32_t; +#define NULL ((void *)0) +#define ZX_HANDLE_INVALID 0 + +#if defined(__clang__) +#define ZX_HANDLE_ACQUIRE __attribute__((acquire_handle("Fuchsia"))) +#define ZX_HANDLE_RELEASE __attribute__((release_handle("Fuchsia"))) +#define ZX_HANDLE_USE __attribute__((use_handle("Fuchsia"))) +#else +#define ZX_HANDLE_ACQUIRE +#define ZX_HANDLE_RELEASE +#define ZX_HANDLE_USE +#endif + +zx_status_t zx_channel_create( + uint32_t options, + zx_handle_t *out0 ZX_HANDLE_ACQUIRE, + zx_handle_t *out1 ZX_HANDLE_ACQUIRE); + +zx_status_t zx_handle_close( + zx_handle_t handle ZX_HANDLE_RELEASE); + +void escape1(zx_handle_t *in); +void escape2(zx_handle_t in); +void (*escape3)(zx_handle_t) = escape2; + +void use1(const zx_handle_t *in ZX_HANDLE_USE); +void use2(zx_handle_t in ZX_HANDLE_USE); + +void moreArgs(zx_handle_t, int, ...); +void lessArgs(zx_handle_t, int a = 5); + +// To test if argument indexes are OK for operator calls. +struct MyType { + ZX_HANDLE_ACQUIRE + zx_handle_t operator+(zx_handle_t ZX_HANDLE_RELEASE replace); +}; + +void checkInvalidHandle01() { + zx_handle_t sa, sb; + zx_channel_create(0, &sa, &sb); + if (sa == ZX_HANDLE_INVALID) + ; + // Will we ever see a warning like below? + // We eagerly replace the symbol with a constant and lose info... + use2(sa); // TODOexpected-warning {{Use of an invalid handle}} + zx_handle_close(sb); + zx_handle_close(sa); +} + +void checkInvalidHandle2() { + zx_handle_t sa, sb; + zx_channel_create(0, &sa, &sb); + if (sb != ZX_HANDLE_INVALID) + zx_handle_close(sb); + if (sa != ZX_HANDLE_INVALID) + zx_handle_close(sa); +} + +void checkNoCrash01() { + zx_handle_t sa, sb; + zx_channel_create(0, &sa, &sb); + moreArgs(sa, 1, 2, 3, 4, 5); + lessArgs(sa); + zx_handle_close(sa); + zx_handle_close(sb); +} + +void checkNoLeak01() { + zx_handle_t sa, sb; + zx_channel_create(0, &sa, &sb); + zx_handle_close(sa); + zx_handle_close(sb); +} + +void checkNoLeak02() { + zx_handle_t ay[2]; + zx_channel_create(0, &ay[0], &ay[1]); + zx_handle_close(ay[0]); + zx_handle_close(ay[1]); +} + +void checkNoLeak03() { + zx_handle_t ay[2]; + zx_channel_create(0, &ay[0], &ay[1]); + for (int i = 0; i < 2; i++) + zx_handle_close(ay[i]); +} + +zx_handle_t checkNoLeak04() { + zx_handle_t sa, sb; + zx_channel_create(0, &sa, &sb); + zx_handle_close(sa); + return sb; // no warning +} + +zx_handle_t checkNoLeak05(zx_handle_t *out1) { + zx_handle_t sa, sb; + zx_channel_create(0, &sa, &sb); + *out1 = sa; + return sb; // no warning +} + +void checkNoLeak06() { + zx_handle_t sa, sb; + if (zx_channel_create(0, &sa, &sb)) + return; + zx_handle_close(sa); + zx_handle_close(sb); +} + +void checkLeak01(int tag) { + zx_handle_t sa, sb; + if (zx_channel_create(0, &sa, &sb)) + return; + use1(&sa); + if (tag) + zx_handle_close(sa); + use2(sb); // expected-warning {{Potential leak of handle}} + zx_handle_close(sb); +} + +void checkDoubleRelease01(int tag) { + zx_handle_t sa, sb; + zx_channel_create(0, &sa, &sb); + if (tag) + zx_handle_close(sa); + zx_handle_close(sa); // expected-warning {{Releasing a previously released handle}} + zx_handle_close(sb); +} + +void checkUseAfterFree01(int tag) { + zx_handle_t sa, sb; + zx_channel_create(0, &sa, &sb); + if (tag) { + zx_handle_close(sa); + use1(&sa); // expected-warning {{Using a previously released handle}} + } + zx_handle_close(sb); + use2(sb); // expected-warning {{Using a previously released handle}} +} + +void checkMemberOperatorIndices() { + zx_handle_t sa, sb, sc; + zx_channel_create(0, &sa, &sb); + zx_handle_close(sb); + MyType t; + sc = t + sa; + zx_handle_close(sc); +} + +// RAII + +template +struct HandleWrapper { + ~HandleWrapper() { close(); } + void close() { + if (handle != ZX_HANDLE_INVALID) + zx_handle_close(handle); + } + T *get_handle_address() { return &handle; } +private: + T handle; +}; + +void doNotWarnOnRAII() { + HandleWrapper w1; + zx_handle_t sb; + if (zx_channel_create(0, w1.get_handle_address(), &sb)) + return; + zx_handle_close(sb); +} + +template +struct HandleWrapperUnkonwDtor { + ~HandleWrapperUnkonwDtor(); + void close() { + if (handle != ZX_HANDLE_INVALID) + zx_handle_close(handle); + } + T *get_handle_address() { return &handle; } +private: + T handle; +}; + +void doNotWarnOnUnkownDtor() { + HandleWrapperUnkonwDtor w1; + zx_handle_t sb; + if (zx_channel_create(0, w1.get_handle_address(), &sb)) + return; + zx_handle_close(sb); +} + +// Various escaping scenarios + +zx_handle_t *get_handle_address(); + +void escape_store_to_escaped_region01() { + zx_handle_t sb; + if (zx_channel_create(0, get_handle_address(), &sb)) + return; + zx_handle_close(sb); +} + +struct object { + zx_handle_t *get_handle_address(); +}; + +void escape_store_to_escaped_region02(object &o) { + zx_handle_t sb; + // Same as above. + if (zx_channel_create(0, o.get_handle_address(), &sb)) + return; + zx_handle_close(sb); +} + +void escape_store_to_escaped_region03(object o) { + zx_handle_t sb; + // Should we consider the pointee of get_handle_address escaped? + // Maybe we only should it consider escaped if o escapes? + if (zx_channel_create(0, o.get_handle_address(), &sb)) + return; + zx_handle_close(sb); +} + +void escape_through_call(int tag) { + zx_handle_t sa, sb; + if (zx_channel_create(0, &sa, &sb)) + return; + escape1(&sa); + if (tag) + escape2(sb); + else + escape3(sb); +} + +struct have_handle { + zx_handle_t h; + zx_handle_t *hp; +}; + +void escape_through_store01(have_handle *handle) { + zx_handle_t sa; + if (zx_channel_create(0, &sa, handle->hp)) + return; + handle->h = sa; +} + +have_handle global; +void escape_through_store02() { + zx_handle_t sa; + if (zx_channel_create(0, &sa, global.hp)) + return; + global.h = sa; +} + +have_handle escape_through_store03() { + zx_handle_t sa, sb; + if (zx_channel_create(0, &sa, &sb)) + return {0, nullptr}; + zx_handle_close(sb); + return {sa, nullptr}; +} + +void escape_structs(have_handle *); +void escape_transitively01() { + zx_handle_t sa, sb; + if (zx_channel_create(0, &sa, &sb)) + return; + have_handle hs[2]; + hs[1] = {sa, &sb}; + escape_structs(hs); +} + +void escape_top_level_pointees(zx_handle_t *h) { + zx_handle_t h2; + if (zx_channel_create(0, h, &h2)) + return; + zx_handle_close(h2); +} // *h should be escaped here. Right?