| //== PointerSortingChecker.cpp --------------------------------- -*- 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 file defines PointerSortingChecker which checks for non-determinism |
| // caused due to sorting containers with pointer-like elements. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
| #include "clang/StaticAnalyzer/Core/Checker.h" |
| #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
| |
| using namespace clang; |
| using namespace ento; |
| using namespace ast_matchers; |
| |
| namespace { |
| |
| // ID of a node at which the diagnostic would be emitted. |
| constexpr llvm::StringLiteral WarnAtNode = "sort"; |
| |
| class PointerSortingChecker : public Checker<check::ASTCodeBody> { |
| public: |
| void checkASTCodeBody(const Decl *D, |
| AnalysisManager &AM, |
| BugReporter &BR) const; |
| }; |
| |
| static void emitDiagnostics(const BoundNodes &Match, const Decl *D, |
| BugReporter &BR, AnalysisManager &AM, |
| const PointerSortingChecker *Checker) { |
| auto *ADC = AM.getAnalysisDeclContext(D); |
| |
| const auto *MarkedStmt = Match.getNodeAs<CallExpr>(WarnAtNode); |
| assert(MarkedStmt); |
| |
| auto Range = MarkedStmt->getSourceRange(); |
| auto Location = PathDiagnosticLocation::createBegin(MarkedStmt, |
| BR.getSourceManager(), |
| ADC); |
| std::string Diagnostics; |
| llvm::raw_string_ostream OS(Diagnostics); |
| OS << "Sorting pointer-like elements " |
| << "can result in non-deterministic ordering"; |
| |
| BR.EmitBasicReport(ADC->getDecl(), Checker, |
| "Sorting of pointer-like elements", "Non-determinism", |
| OS.str(), Location, Range); |
| } |
| |
| auto callsName(const char *FunctionName) -> decltype(callee(functionDecl())) { |
| return callee(functionDecl(hasName(FunctionName))); |
| } |
| |
| // FIXME: Currently we simply check if std::sort is used with pointer-like |
| // elements. This approach can have a big false positive rate. Using std::sort, |
| // std::unique and then erase is common technique for deduplicating a container |
| // (which in some cases might even be quicker than using, let's say std::set). |
| // In case a container contains arbitrary memory addresses (e.g. multiple |
| // things give different stuff but might give the same thing multiple times) |
| // which we don't want to do things with more than once, we might use |
| // sort-unique-erase and the sort call will emit a report. |
| auto matchSortWithPointers() -> decltype(decl()) { |
| // Match any of these function calls. |
| auto SortFuncM = anyOf( |
| callsName("std::is_sorted"), |
| callsName("std::nth_element"), |
| callsName("std::partial_sort"), |
| callsName("std::partition"), |
| callsName("std::sort"), |
| callsName("std::stable_partition"), |
| callsName("std::stable_sort") |
| ); |
| |
| // Match only if the container has pointer-type elements. |
| auto IteratesPointerEltsM = hasArgument(0, |
| hasType(cxxRecordDecl(has( |
| fieldDecl(hasType(hasCanonicalType( |
| pointsTo(hasCanonicalType(pointerType())) |
| ))) |
| )))); |
| |
| auto PointerSortM = stmt(callExpr(allOf(SortFuncM, IteratesPointerEltsM)) |
| ).bind(WarnAtNode); |
| |
| return decl(forEachDescendant(PointerSortM)); |
| } |
| |
| void PointerSortingChecker::checkASTCodeBody(const Decl *D, |
| AnalysisManager &AM, |
| BugReporter &BR) const { |
| auto MatcherM = matchSortWithPointers(); |
| |
| auto Matches = match(MatcherM, *D, AM.getASTContext()); |
| for (const auto &Match : Matches) |
| emitDiagnostics(Match, D, BR, AM, this); |
| } |
| |
| } // end of anonymous namespace |
| |
| void ento::registerPointerSortingChecker(CheckerManager &Mgr) { |
| Mgr.registerChecker<PointerSortingChecker>(); |
| } |
| |
| bool ento::shouldRegisterPointerSortingChecker(const LangOptions &LO) { |
| return LO.CPlusPlus; |
| } |