Index: lib/StaticAnalyzer/Checkers/MagentaHandleChecker.cpp =================================================================== --- lib/StaticAnalyzer/Checkers/MagentaHandleChecker.cpp +++ lib/StaticAnalyzer/Checkers/MagentaHandleChecker.cpp @@ -119,7 +119,7 @@ // Functions that do not have any annotation at all UNANNOTATED_FUNC, // Functions have annotations, but cannot be processed correctly due to - // mismatch to the arguments. + // mismatches to the arguments. UNPROCESSED_FUNC, // When a bug is found in function with this flag, do not report this bug // Used to suppress known false positives. @@ -192,11 +192,55 @@ } }; +class HandleState { +private: + enum Kind { + // Handle is allocated + Allocated, + // Handle is released + Released, + // Handle is no longer trackable + Escaped + } K; + // To record allocation site information. + const MemRegion *Region; + HandleState(Kind k, const MemRegion *region) : K(k), Region(region) {} + +public: + bool operator==(const HandleState &X) const { return K == X.K; } + bool isAllocated() const { return K == Allocated; } + bool isReleased() const { return K == Released; } + bool isEscaped() const { return K == Escaped; } + const MemRegion *getRegion() const { return Region; } + + static HandleState getAllocated(const MemRegion *region) { + return HandleState(Allocated, region); + } + + static HandleState getReleased(const HandleState *HS) { + return HandleState(Released, HS->getRegion()); + } + + static HandleState getEscaped(const HandleState *HS) { + return HandleState(Escaped, HS->getRegion()); + } + + void Profile(llvm::FoldingSetNodeID &ID) const { + ID.AddInteger(K); + ID.AddPointer(Region); + } +}; + static const char *ANNOTATION_PREFIX = "mx_"; static const char *HANDLE_TYPE_NAME = "mx_handle_t"; static const char *SYSCALL_RETURN_TYPE_NAME = "mx_status_t"; -class MagentaHandleChecker : public Checker { +class MagentaHandleChecker + : public Checker { + std::unique_ptr LeakBugType; + std::unique_ptr DoubleReleaseBugType; + std::unique_ptr UseAfterFreeBugType; mutable llvm::DenseMap FuncDeclMap; mutable llvm::StringMap SpecialFuncDeclMap; @@ -206,20 +250,83 @@ public: MagentaHandleChecker(); // Checker callbacks + void checkPostCall(const CallEvent &Call, CheckerContext &Ctx) const; + void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &Ctx) const; bool evalCall(const CallExpr *CE, CheckerContext &Ctx) const; + ProgramStateRef checkPointerEscape(ProgramStateRef State, + const InvalidatedSymbols &Escaped, + const CallEvent *Call, + PointerEscapeKind Kind) const; private: // Evaluate handle related function call with annotation information bool evalCallWithFuncSpec(const CallExpr *CE, const FunctionDecl *FD, CheckerContext &Ctx, FuncSpec &FS) const; + // Process handle escapes that are not processed by checkPointerEscape() + void processUninlinedCalls(ProgramStateRef State, const CallEvent &Call, + CheckerContext &Ctx) const; + + // Bug reporting functions + void reportLeaks(ArrayRef LeakedHandles, CheckerContext &Ctx, + ExplodedNode *ErrorNode) const; + + void reportDoubleRelease(SymbolRef HandleSym, const SourceRange &Range, + CheckerContext &Ctx) const; + + void reportUseAfterFree(SymbolRef HandleSym, const SourceRange &Range, + CheckerContext &Ctx) const; + + void reportBug(SymbolRef Sym, ExplodedNode *ErrorNode, CheckerContext &C, + const SourceRange *Range, const std::unique_ptr &Type, + StringRef Msg) const; + // Utility functions // Generate function spec info based on inline annotations in declaration bool generateSpecForFuncionDecl(const FunctionDecl *FuncDecl) const; // Extract magenta related annotation string from declarations StringRef extractAnnotationWithoutPrefix(const Decl *D) const; + + ProgramStateRef processHandleArrayArgWithFuncSpec(const CallExpr *CE, + ProgramStateRef State, + CheckerContext &Ctx, + ArgSpec &CurArgSpec) const; + + ProgramStateRef conjureFailedState(const CallExpr *CE, ProgramStateRef State, + CheckerContext &Ctx) const; + + ProgramStateRef allocateSingleHandle(const CallExpr *CE, const Expr *ArgExpr, + ProgramStateRef State, + CheckerContext &Ctx) const; + + // Return true only if it is certain that Sym will be Zero + static bool CheckSymbolConstraintToZero(SymbolRef Sym, ProgramStateRef State); + + bool isLeaked(SymbolRef SymHandle, const HandleState &CurHandleState, + bool isSymDead, ProgramStateRef State) const; + + // This is how CSA determines if a pointer is escaped in assignment + // statement. Copied from ExprEngine::processPointerEscapedOnBind + bool isArgumentEscaped(SVal Loc) const { + if (Optional regionLoc = + Loc.getAs()) { + if (!regionLoc->getRegion()->hasStackStorage()) + return true; + } + return false; + } }; } // end anonymous namespace +// Handle -> HandleState map +REGISTER_MAP_WITH_PROGRAMSTATE(HStateMap, SymbolRef, HandleState) + MagentaHandleChecker::MagentaHandleChecker() { + // Initialize the bug types. + LeakBugType.reset( + new BugType(this, "MX handle leak", "Magenta Handle Error")); + DoubleReleaseBugType.reset( + new BugType(this, "MX handle double release", "Magenta Handle Error")); + UseAfterFreeBugType.reset( + new BugType(this, "MX handle use after free", "Magenta Handle Error")); // Initialize the map for supported annotations. AnnotationMap = {{"handle_acquire", ALLOCATE}, @@ -283,17 +390,302 @@ return false; } +void MagentaHandleChecker::checkPostCall(const CallEvent &Call, + CheckerContext &Ctx) const { + ProgramStateRef State = Ctx.getState(); + const FunctionDecl *FD = dyn_cast_or_null(Call.getDecl()); + if (!FD || !FuncKindMap.count(FD) || (FuncKindMap[FD] != ANNOATATED_FUNC)) { + processUninlinedCalls(State, Call, Ctx); + } +} + +ProgramStateRef MagentaHandleChecker::checkPointerEscape( + ProgramStateRef State, const InvalidatedSymbols &Escaped, + const CallEvent *Call, PointerEscapeKind Kind) const { + for (const SymbolRef &EscapedSymbol : Escaped) { + const HandleState *CurHS = State->get(EscapedSymbol); + if (CurHS) { + State = State->remove(EscapedSymbol); + State = + State->set(EscapedSymbol, HandleState::getEscaped(CurHS)); + } + } + return State; +} + +void MagentaHandleChecker::checkDeadSymbols(SymbolReaper &SymReaper, + CheckerContext &Ctx) const { + ProgramStateRef State = Ctx.getState(); + SmallVector LeakVector; + HStateMapTy TrackedHandle = State->get(); + for (auto &CurItem : TrackedHandle) { + SymbolRef Sym = CurItem.first; + // Workaround for zombie symbol issue. + bool IsSymDead = SymReaper.maybeDead(Sym); + const HandleState &CurHandleState = CurItem.second; + if (isLeaked(Sym, CurHandleState, IsSymDead, State)) + LeakVector.push_back(Sym); + if (IsSymDead) + State = State->remove(Sym); + } + if (!LeakVector.empty()) { + ExplodedNode *N = Ctx.generateNonFatalErrorNode(State); + if (!N) + return; + reportLeaks(LeakVector, Ctx, N); + } + Ctx.addTransition(State); +} + // Evaluate functions with annotation attributes. // If process failed, fallback to conservativeEvalCall by returning false. bool MagentaHandleChecker::evalCallWithFuncSpec(const CallExpr *CE, const FunctionDecl *FD, CheckerContext &Ctx, FuncSpec &FS) const { - // Here is just a place holder. The real handle analysis code will be - // included in follow up commits + if (FS.RetAction == DONOTREPORT) { + FuncKindMap[FD] = DONOTREPORT_FUNC; + return false; + } + SmallVector invalidateSVal; + ProgramStateRef State = Ctx.getState(); + ASTContext &ASTCtx = State->getStateManager().getContext(); + const LocationContext *LCtx = Ctx.getLocationContext(); + + // Check the return type + bool IsRetTypeInt = false; + QualType RetType = CE->getCallReturnType(ASTCtx); + if (RetType->isIntegralType(ASTCtx)) + IsRetTypeInt = true; + + // Conjure a failed state if this function has this flag and return type is + // int. When failed, the return value is less than 0 + ProgramStateRef FailedState = nullptr; + if (IsRetTypeInt && FS.RetAction == BIFURCATE) + FailedState = conjureFailedState(CE, State, Ctx); + + // Invalidate the pointers arguments that are not annotated + llvm::SmallSet PreserveArgs; + for (ArgSpec &CurArgSpec : FS.ArgSpecVec) { + if (CurArgSpec.getIndex() != -1) + PreserveArgs.insert(CurArgSpec.getIndex()); + if (CurArgSpec.isArray()) { + if (CurArgSpec.getInputCArgIdx() != -1) + PreserveArgs.insert(CurArgSpec.getInputCArgIdx()); + if (CurArgSpec.getOutputCArgIdx() != -1) + PreserveArgs.insert(CurArgSpec.getOutputCArgIdx()); + } + } + for (int Idx = 0, Count = CE->getNumArgs(); Idx < Count; ++Idx) { + if (!PreserveArgs.count(Idx)) { + const Expr *CurHandleExpr = CE->getArg(Idx); + if (!CurHandleExpr) { + FuncKindMap[FD] = UNPROCESSED_FUNC; + return false; + } + SVal CurArgSVal = State->getSVal(CurHandleExpr, LCtx); + invalidateSVal.push_back(CurArgSVal); + } + } + if (invalidateSVal.size()) + State = State->invalidateRegions(invalidateSVal, CE, Ctx.blockCount(), LCtx, + true, nullptr, nullptr, nullptr); + + // Return 0 in successful state + if (IsRetTypeInt && FS.RetAction == BIFURCATE) { + SVal StatusSVal = Ctx.getSValBuilder().makeIntVal(llvm::APSInt::get(0)); + State = State->BindExpr(CE, LCtx, StatusSVal); + } else { + SVal StatusSVal = Ctx.getSValBuilder().conjureSymbolVal(nullptr, CE, LCtx, + Ctx.blockCount()); + State = State->BindExpr(CE, LCtx, StatusSVal); + } + + for (ArgSpec &CurArgSpec : FS.ArgSpecVec) { + const Expr *CurHandleExpr = CE->getArg(CurArgSpec.getIndex()); + if (CurArgSpec.isArray()) { + // Special treatment for array of handles + State = processHandleArrayArgWithFuncSpec(CE, State, Ctx, CurArgSpec); + if (!State) + return false; + continue; + } + // Not an array of handles + if (CurArgSpec.isAllocation()) { + // Single handle acquire + State = allocateSingleHandle(CE, CurHandleExpr, State, Ctx); + if (!State) + return false; + } else if (CurArgSpec.isEscape() || CurArgSpec.isRelease() || + CurArgSpec.isNoEscape()) { + SVal SValHandle; + if (CurArgSpec.isPointer()) { + SVal ArgSVal = State->getSVal(CurHandleExpr, LCtx); + const MemRegion *MemHandle = ArgSVal.getAsRegion(); + if (!MemHandle) + return false; + SValHandle = State->getSVal(MemHandle); + } else { + SValHandle = State->getSVal(CurHandleExpr, LCtx); + } + SymbolRef SymHandle = SValHandle.getAsSymbol(); + if (!SymHandle) + return false; + const HandleState *CurState = State->get(SymHandle); + if (!CurState) + continue; + // Check use after release + if ((CurArgSpec.isEscape() || CurArgSpec.isNoEscape()) && + CurState->isReleased()) + reportUseAfterFree(SymHandle, CE->getSourceRange(), Ctx); + + if ((CurArgSpec.isEscape())) { + // Handle escape through value + State = + State->set(SymHandle, HandleState::getEscaped(CurState)); + } else if (CurArgSpec.isRelease()) { + if (CurState->isReleased()) + reportDoubleRelease(SymHandle, CE->getSourceRange(), Ctx); + State = State->set(SymHandle, + HandleState::getReleased(CurState)); + } + } + } + + if (FailedState) + Ctx.addTransition(FailedState); + Ctx.addTransition(State); FuncKindMap[FD] = ANNOATATED_FUNC; + return true; +} + +ProgramStateRef MagentaHandleChecker::processHandleArrayArgWithFuncSpec( + const CallExpr *CE, ProgramStateRef State, CheckerContext &Ctx, + ArgSpec &CurArgSpec) const { + // A place holder. This function is used to process handle acquire/release + // via an array. Its code will be included in follow up commits + return nullptr; +} + +// Process the handle escaped situation. +// When a function is not inlined for any reasons, if one of its +// arguments is an acquired handle, treat it as an escaped handle. +// It should be processed by checkPointerEscape in the future. +void MagentaHandleChecker::processUninlinedCalls(ProgramStateRef State, + const CallEvent &Call, + CheckerContext &Ctx) const { + if (Ctx.wasInlined) + return; + unsigned ArgsCount = Call.getNumArgs(); + bool StateChanged = false; + if (!ArgsCount) + // No argument, ignore it + return; + for (unsigned i = 0; i < ArgsCount; i++) { + SymbolRef ArgSym = Call.getArgSVal(i).getAsSymbol(); + // Check if the argument is a pointer. If so, it should have been handled + // by checkPointerEscape, can be ignored here. + if (!ArgSym) + continue; + + const HandleState *CurHandleState = State->get(ArgSym); + // Check if the handle is tracked by analyzer. If not, we ignore it here. + if (!CurHandleState) + continue; + + if (CurHandleState->isReleased()) + // Use after free + reportUseAfterFree(ArgSym, Call.getSourceRange(), Ctx); + State = + State->set(ArgSym, HandleState::getEscaped(CurHandleState)); + StateChanged = true; + } + if (StateChanged) + Ctx.addTransition(State); +} + +// When a magenta syscall failed. It will return an integer less than 0. +ProgramStateRef MagentaHandleChecker::conjureFailedState( + const CallExpr *CE, ProgramStateRef State, CheckerContext &Ctx) const { + const LocationContext *LCtx = Ctx.getLocationContext(); + DefinedOrUnknownSVal FailedSVal = Ctx.getSValBuilder().conjureSymbolVal( + nullptr, CE, LCtx, Ctx.blockCount()); + ProgramStateRef FailedState = State->BindExpr(CE, LCtx, FailedSVal); + FailedState = FailedState->assumeInclusiveRange( + FailedSVal, llvm::APSInt::get(INT_MIN), llvm::APSInt::get(-1), true); + return FailedState; +} + +ProgramStateRef MagentaHandleChecker::allocateSingleHandle( + const CallExpr *CE, const Expr *ArgExpr, ProgramStateRef State, + CheckerContext &Ctx) const { + const LocationContext *LCtx = Ctx.getLocationContext(); + SValBuilder &Bldr = Ctx.getSValBuilder(); + if (!State || !LCtx || !CE) + return nullptr; + SVal ArgSVal = State->getSVal(ArgExpr, LCtx); + // It is a pointer, so get the mem region first. + const MemRegion *MemHandle = ArgSVal.getAsRegion(); + if (!MemHandle) + return nullptr; + // Retrive thet type of handle + QualType HandleType = ArgExpr->getType(); + if (!HandleType->isPointerType()) + return nullptr; + HandleType = dyn_cast(HandleType.getTypePtr())->getPointeeType(); + // Bind allocated handle + Loc HandleLoc = Bldr.makeLoc(MemHandle); + DefinedOrUnknownSVal ConjuredHandleSVal = + Bldr.conjureSymbolVal(MemHandle, CE, LCtx, HandleType, Ctx.blockCount()); + State = State->bindLoc(HandleLoc, ConjuredHandleSVal, LCtx); + + SymbolRef SymHandle = ConjuredHandleSVal.getAsSymbol(); + if (!SymHandle) + return nullptr; + if (ConjuredHandleSVal.getBaseKind() == SVal::NonLocKind) + // Allocated handle should be larger than 0 + State = + State->assumeInclusiveRange(ConjuredHandleSVal, llvm::APSInt::get(1), + llvm::APSInt::get(INT_MAX), true); + + HandleState HS = HandleState::getAllocated(MemHandle); + // Workaround for a rare case + // TopFunction(mx_handle_t * out) { + // handle_allocate(out); + // } + // It should be considered as handle escape. + if (isArgumentEscaped(ArgSVal)) + return State->set(SymHandle, HandleState::getEscaped(&HS)); + return State->set(SymHandle, HS); +} + +bool MagentaHandleChecker::isLeaked(SymbolRef SymHandle, + const HandleState &CurHandleState, + bool isSymDead, + ProgramStateRef State) const { + if (isSymDead && CurHandleState.isAllocated()) { + // Check if handle value is MX_HANDLE_INVALID. If so, not a leak. + if (CheckSymbolConstraintToZero(SymHandle, State)) + return false; + // Handle is allocated and dead. Leaked + return true; + } + // Handle not dead. Not a leak. return false; } + +bool MagentaHandleChecker::CheckSymbolConstraintToZero(SymbolRef Sym, + ProgramStateRef State) { + ConstraintManager &CMgr = State->getConstraintManager(); + ConditionTruthVal IsZero = CMgr.isNull(State, Sym); + if (IsZero.isConstrained()) { + // The value can be compared to zero + if (IsZero.isConstrainedTrue()) + return true; + } + return false; +} + // Parse annotation attributes from given FunctionDecl. If the annotation is // not valid or no annotation available, return false. bool MagentaHandleChecker::generateSpecForFuncionDecl( @@ -388,6 +780,49 @@ return StringRef(); } +void MagentaHandleChecker::reportBug(SymbolRef Sym, ExplodedNode *ErrorNode, + CheckerContext &C, + const SourceRange *Range, + const std::unique_ptr &Type, + StringRef Msg) const { + if (!ErrorNode || !Sym) + return; + auto R = llvm::make_unique(*Type, Msg, ErrorNode); + if (Range) + R->addRange(*Range); + R->markInteresting(Sym); + C.emitReport(std::move(R)); +} + +void MagentaHandleChecker::reportLeaks(ArrayRef LeakedHandles, + CheckerContext &C, + ExplodedNode *ErrorNode) const { + for (SymbolRef LeakedHandle : LeakedHandles) { + reportBug(LeakedHandle, ErrorNode, C, nullptr, LeakBugType, + "Potential leak of handle"); + } +} + +void MagentaHandleChecker::reportDoubleRelease(SymbolRef HandleSym, + const SourceRange &Range, + CheckerContext &C) const { + ExplodedNode *ErrNode = C.generateErrorNode(); + if (!ErrNode) + return; + reportBug(HandleSym, ErrNode, C, &Range, DoubleReleaseBugType, + "Releasing a previously released handle"); +} + +void MagentaHandleChecker::reportUseAfterFree(SymbolRef HandleSym, + const SourceRange &Range, + CheckerContext &C) const { + ExplodedNode *ErrNode = C.generateErrorNode(); + if (!ErrNode) + return; + reportBug(HandleSym, ErrNode, C, &Range, UseAfterFreeBugType, + "Using a previously released handle"); +} + void ento::registerMagentaHandleChecker(CheckerManager &mgr) { mgr.registerChecker(); } Index: test/Analysis/mxhandle.c =================================================================== --- /dev/null +++ test/Analysis/mxhandle.c @@ -0,0 +1,192 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.magenta.MagentaHandleChecker -analyzer-store=region -verify %s + +typedef __typeof__(sizeof(int)) size_t; +typedef int mx_status_t; +typedef __typeof__(sizeof(int)) mx_handle_t; +typedef unsigned int uint32_t; +#define NULL ((void *)0) + +#if defined(__clang__) +#define MX_SYSCALL_PARAM_ATTR(x) __attribute__((annotate("mx_" #x))) +#else +#define MX_SYSCALL_PARAM_ATTR(x) // no-op +#endif + +extern mx_status_t mx_channel_create( +uint32_t options, + MX_SYSCALL_PARAM_ATTR(handle_acquire) mx_handle_t* out0, + MX_SYSCALL_PARAM_ATTR(handle_acquire) mx_handle_t* out1); + +extern mx_status_t mx_handle_close( +MX_SYSCALL_PARAM_ATTR(handle_release_always) mx_handle_t handle); + +// Escape is the default behavior for any function take a handle as parameter +// It should work with/without explicit annotations +void escapeMethod01(mx_handle_t *in); + +void escapeMethod02( +MX_SYSCALL_PARAM_ATTR(handle_escape) mx_handle_t *in); + +void escapeMethod03(mx_handle_t in); + +void escapeMethod04( +MX_SYSCALL_PARAM_ATTR(handle_escape) mx_handle_t in); + +// Handle should not escape when mx_handle_use is present. It should work +// for both value type and pointer type arguments. +void useHandle01( +MX_SYSCALL_PARAM_ATTR(handle_use) mx_handle_t handle); + +void useHandle02( +MX_SYSCALL_PARAM_ATTR(handle_use) mx_handle_t *handle); + +// End of declaration + +void checkNoLeak01() { + mx_handle_t sa, sb; + mx_channel_create(0, &sa, &sb); + mx_handle_close(sa); + mx_handle_close(sb); +} + +void checkNoLeak02() { + mx_handle_t ay[2]; + mx_channel_create(0, &ay[0], &ay[1]); + mx_handle_close(ay[0]); + mx_handle_close(ay[1]); +} + +void checkNoLeak03() { + mx_handle_t ay[2]; + mx_channel_create(0, &ay[0], &ay[1]); + for (int i = 0; i < 2; i++) { + mx_handle_close(ay[i]); + } +} + +mx_handle_t checkNoLeak04() { + mx_handle_t sa, sb; + mx_channel_create(0, &sa, &sb); + mx_handle_close(sa); + return sb; // no warning +} + +mx_handle_t checkNoLeak05(mx_handle_t *out1) { + mx_handle_t sa, sb; + mx_channel_create(0, &sa, &sb); + *out1 = sa; + return sb; // no warning +} + +void checkNoLeak10() { + mx_handle_t sa, sb; + if (mx_channel_create(0, &sa, &sb) < 0) { + return; + } + mx_handle_close(sa); + mx_handle_close(sb); +} + +void checkNoLeak12(int tag) { + mx_handle_t sa, sb; + if (mx_channel_create(0, &sa, &sb) < 0) { + return; + } + if (tag) { + escapeMethod01(&sa); + escapeMethod01(&sb); + } + mx_handle_close(sa); + mx_handle_close(sb); +} + +void checkNoLeak13(int tag) { + mx_handle_t sa, sb; + if (mx_channel_create(0, &sa, &sb) < 0) { + return; + } + if (tag) { + escapeMethod02(&sa); + escapeMethod02(&sb); + } + mx_handle_close(sa); + mx_handle_close(sb); +} + +void checkNoLeak14(int tag) { + mx_handle_t sa, sb; + if (mx_channel_create(0, &sa, &sb) < 0) { + return; + } + if (tag) { + escapeMethod03(sa); + escapeMethod03(sb); + } + mx_handle_close(sa); + mx_handle_close(sb); +} + +void checkNoLeak15(int tag) { + mx_handle_t sa, sb; + if (mx_channel_create(0, &sa, &sb) < 0) { + return; + } + if (tag) { + escapeMethod04(sa); + escapeMethod04(sb); + } + mx_handle_close(sa); + mx_handle_close(sb); +} + +void checkLeak01() { + mx_handle_t sa, sb; + mx_channel_create(0, &sa, &sb); +} // expected-warning {{Potential leak of handle}} + + +void checkLeak02(int tag) { + mx_handle_t sa, sb; + mx_channel_create(0, &sa, &sb); + if (tag) { + mx_handle_close(sa); + } + mx_handle_close(sb); // expected-warning {{Potential leak of handle}} +} + +void checkLeak07() { + mx_handle_t sa, sb; + mx_channel_create(0, &sa, &sb); + + useHandle01(sa); + mx_handle_close(sb); // expected-warning {{Potential leak of handle}} +} + +void checkLeak08() { + mx_handle_t sa, sb; + mx_channel_create(0, &sa, &sb); + + useHandle02(&sa); + mx_handle_close(sb); // expected-warning {{Potential leak of handle}} +} + +void checkDoubleRelease01(int tag) { + mx_handle_t sa, sb; + mx_channel_create(0, &sa, &sb); + if (tag) { + mx_handle_close(sa); + } + mx_handle_close(sa); // expected-warning {{Releasing a previously released handle}} + mx_handle_close(sb); +} + +void checkUseAfterFree01(int tag) { + mx_handle_t sa, sb; + mx_channel_create(0, &sa, &sb); + if (tag) { + mx_handle_close(sa); + } + useHandle01(sa); // expected-warning {{Using a previously released handle}} + mx_handle_close(sa); + mx_handle_close(sb); +}