Index: clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h =================================================================== --- clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h +++ clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h @@ -386,7 +386,7 @@ /// to the user. This method allows to rest the location which should be used /// for uniquing reports. For example, memory leaks checker, could set this to /// the allocation site, rather then the location where the bug is reported. - PathSensitiveBugReport(BugType &bt, StringRef desc, + PathSensitiveBugReport(const BugType &bt, StringRef desc, const ExplodedNode *errorNode, PathDiagnosticLocation LocationToUnique, const Decl *DeclToUnique) Index: clang/lib/StaticAnalyzer/Checkers/FuchsiaHandleChecker.cpp =================================================================== --- clang/lib/StaticAnalyzer/Checkers/FuchsiaHandleChecker.cpp +++ clang/lib/StaticAnalyzer/Checkers/FuchsiaHandleChecker.cpp @@ -170,7 +170,7 @@ /*SuppressOnSink=*/true}; BugType DoubleReleaseBugType{this, "Fuchsia handle double release", "Fuchsia Handle Error"}; - BugType UseAfterFreeBugType{this, "Fuchsia handle use after release", + BugType UseAfterRelease{this, "Fuchsia handle use after release", "Fuchsia Handle Error"}; public: @@ -204,6 +204,32 @@ REGISTER_MAP_WITH_PROGRAMSTATE(HStateMap, SymbolRef, HandleState) +static const ExplodedNode *getAcquireSite(const ExplodedNode *N, SymbolRef Sym, + CheckerContext &Ctx) { + const ExplodedNode *AcquireNode = N; + + ProgramStateRef State = N->getState(); + // When bug type is handle leak, exploded node N does not have state info for + // leaking handle. Get the predecessor of N instead. + if (!State->get(Sym)) { + N = N->pred_empty() ? nullptr : *(N->pred_begin()); + } + + const ExplodedNode *Pred = N; + while (N) { + State = N->getState(); + if (!State->get(Sym)) { + const HandleState *HState = Pred->getState()->get(Sym); + if (HState && (HState->isAllocated() || HState->maybeAllocated())) + AcquireNode = N; + break; + } + Pred = N; + N = N->pred_empty() ? nullptr : *(N->pred_begin()); + } + return AcquireNode; +} + /// Returns the symbols extracted from the argument and its corresponding /// parameter type or null if it cannot be found. std::pair getFuchsiaHandleSymbol(QualType QT, SVal Arg, @@ -282,6 +308,7 @@ ProgramStateRef State = C.getState(); + std::vector> notes; SymbolRef ResultSymbol = nullptr; if (const auto *TypeDefTy = FuncDecl->getReturnType()->getAs()) if (TypeDefTy->getDecl()->getName() == ErrorTypeName) @@ -308,15 +335,43 @@ if (HState && (HState->isReleased() || HState->maybeReleased())) { reportDoubleRelease(Handle.first, Call.getArgSourceRange(Arg), C); return; - } else + } else { + notes.push_back([Handle](BugReport &BR) { + auto *PathBR = static_cast(&BR); + if (auto IsInteresting = + PathBR->getInterestingnessKind(Handle.first)) { + return "Handle released here."; + } else + return ""; + }); State = State->set( Handle.first, HandleState::getMaybeReleased(ResultSymbol)); + } } else if (Handle.second->hasAttr(attr::AcquireHandle)) { + notes.push_back([Handle](BugReport &BR) { + auto *PathBR = static_cast(&BR); + if (auto IsInteresting = PathBR->getInterestingnessKind(Handle.first)) { + return "Handle allocated here."; + } else + return ""; + }); State = State->set( Handle.first, HandleState::getMaybeAllocated(ResultSymbol)); } } - C.addTransition(State); + const NoteTag *T = C.getNoteTag([this, notes](BugReport &BR) -> std::string { + if (&BR.getBugType() != &UseAfterRelease && + &BR.getBugType() != &LeakBugType && + &BR.getBugType() != &DoubleReleaseBugType) + return ""; + for (auto ¬e : notes) { + std::string text = note(BR); + if (!text.empty()) + return text; + } + return ""; + }); + C.addTransition(State, T); } void FuchsiaHandleChecker::checkDeadSymbols(SymbolReaper &SymReaper, @@ -342,6 +397,7 @@ ProgramStateRef FuchsiaHandleChecker::evalAssume(ProgramStateRef State, SVal Cond, bool Assumption) const { + // TODO: add notes about successes/fails for APIs. ConstraintManager &Cmr = State->getConstraintManager(); HStateMapTy TrackedHandles = State->get(); for (auto &CurItem : TrackedHandles) { @@ -423,7 +479,7 @@ const SourceRange &Range, CheckerContext &C) const { ExplodedNode *ErrNode = C.generateErrorNode(C.getState()); - reportBug(HandleSym, ErrNode, C, &Range, UseAfterFreeBugType, + reportBug(HandleSym, ErrNode, C, &Range, UseAfterRelease, "Using a previously released handle"); } @@ -434,7 +490,22 @@ if (!ErrorNode) return; - auto R = std::make_unique(Type, Msg, ErrorNode); + std::unique_ptr 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( + Type, Msg, ErrorNode, LocUsedForUniqueing, + AcquireNode->getLocationContext()->getDecl()); + } + } + if (!R) + R = std::make_unique(Type, Msg, ErrorNode); if (Range) R->addRange(*Range); R->markInteresting(Sym); Index: clang/test/Analysis/fuchsia_handle.cpp =================================================================== --- clang/test/Analysis/fuchsia_handle.cpp +++ clang/test/Analysis/fuchsia_handle.cpp @@ -1,4 +1,5 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=core,fuchsia.HandleChecker -verify %s +// RUN: %clang_analyze_cc1 -analyzer-checker=core,fuchsia.HandleChecker -analyzer-output=text \ +// RUN: -verify %s typedef __typeof__(sizeof(int)) size_t; typedef int zx_status_t; @@ -101,31 +102,73 @@ void checkLeak01(int tag) { zx_handle_t sa, sb; zx_channel_create(0, &sa, &sb); + // expected-note@-1 {{Handle allocated here}} use1(&sa); - if (tag) + if (tag) // expected-note {{Assuming 'tag' is 0}} zx_handle_close(sa); + // expected-note@-2 {{Taking false branch}} use2(sb); // expected-warning {{Potential leak of handle}} + // expected-note@-1 {{Potential leak of handle}} zx_handle_close(sb); } +void checkReportLeakOnOnePath(int tag) { + zx_handle_t sa, sb; + zx_channel_create(0, &sa, &sb); + // expected-note@-1 {{Handle allocated here}} + zx_handle_close(sb); + switch(tag) { // expected-note {{Control jumps to the 'default' case at line}} + case 0: + use2(sa); + return; + case 1: + use2(sa); + return; + case 2: + use2(sa); + return; + case 3: + use2(sa); + return; + case 4: + use2(sa); + return; + default: + use2(sa); + return; // expected-warning {{Potential leak of handle}} + // expected-note@-1 {{Potential leak of handle}} + } +} + void checkDoubleRelease01(int tag) { zx_handle_t sa, sb; zx_channel_create(0, &sa, &sb); - if (tag) - zx_handle_close(sa); + // expected-note@-1 {{Handle allocated here}} + if (tag) // expected-note {{Assuming 'tag' is not equal to 0}} + zx_handle_close(sa); // expected-note {{Handle released here}} + // expected-note@-2 {{Taking true branch}} zx_handle_close(sa); // expected-warning {{Releasing a previously released handle}} + // expected-note@-1 {{Releasing a previously released handle}} zx_handle_close(sb); } void checkUseAfterFree01(int tag) { zx_handle_t sa, sb; zx_channel_create(0, &sa, &sb); + // expected-note@-1 {{Handle allocated here}} + // expected-note@-2 {{Handle allocated here}} + // expected-note@+2 {{Taking true branch}} + // expected-note@+1 {{Taking false branch}} if (tag) { - zx_handle_close(sa); + // expected-note@-1 {{Assuming 'tag' is not equal to 0}} + zx_handle_close(sa); // expected-note {{Handle released here}} use1(&sa); // expected-warning {{Using a previously released handle}} + // expected-note@-1 {{Using a previously released handle}} } - zx_handle_close(sb); + // expected-note@-6 {{Assuming 'tag' is 0}} + zx_handle_close(sb); // expected-note {{Handle released here}} use2(sb); // expected-warning {{Using a previously released handle}} + // expected-note@-1 {{Using a previously released handle}} } void checkMemberOperatorIndices() {