blob: 348552ba73a9b44dc305eea523e910ffdaabe2e7 [file] [log] [blame]
//===- WorkList.cpp - Analyzer work-list implementation--------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Defines different worklist implementations for the static analyzer.
//
//===----------------------------------------------------------------------===//
#include "clang/StaticAnalyzer/Core/PathSensitive/WorkList.h"
#include "llvm/ADT/PriorityQueue.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/Statistic.h"
#include <deque>
#include <vector>
using namespace clang;
using namespace ento;
#define DEBUG_TYPE "WorkList"
STATISTIC(MaxQueueSize, "Maximum size of the worklist");
STATISTIC(MaxReachableSize, "Maximum size of auxiliary worklist set");
//===----------------------------------------------------------------------===//
// Worklist classes for exploration of reachable states.
//===----------------------------------------------------------------------===//
namespace {
class DFS : public WorkList {
SmallVector<WorkListUnit, 20> Stack;
public:
bool hasWork() const override {
return !Stack.empty();
}
void enqueue(const WorkListUnit& U) override {
Stack.push_back(U);
}
WorkListUnit dequeue() override {
assert(!Stack.empty());
const WorkListUnit& U = Stack.back();
Stack.pop_back(); // This technically "invalidates" U, but we are fine.
return U;
}
};
class BFS : public WorkList {
std::deque<WorkListUnit> Queue;
public:
bool hasWork() const override {
return !Queue.empty();
}
void enqueue(const WorkListUnit& U) override {
Queue.push_back(U);
}
WorkListUnit dequeue() override {
WorkListUnit U = Queue.front();
Queue.pop_front();
return U;
}
};
} // namespace
// Place the dstor for WorkList here because it contains virtual member
// functions, and we the code for the dstor generated in one compilation unit.
WorkList::~WorkList() = default;
std::unique_ptr<WorkList> WorkList::makeDFS() {
return std::make_unique<DFS>();
}
std::unique_ptr<WorkList> WorkList::makeBFS() {
return std::make_unique<BFS>();
}
namespace {
class BFSBlockDFSContents : public WorkList {
std::deque<WorkListUnit> Queue;
SmallVector<WorkListUnit, 20> Stack;
public:
bool hasWork() const override {
return !Queue.empty() || !Stack.empty();
}
void enqueue(const WorkListUnit& U) override {
if (U.getNode()->getLocation().getAs<BlockEntrance>())
Queue.push_front(U);
else
Stack.push_back(U);
}
WorkListUnit dequeue() override {
// Process all basic blocks to completion.
if (!Stack.empty()) {
const WorkListUnit& U = Stack.back();
Stack.pop_back(); // This technically "invalidates" U, but we are fine.
return U;
}
assert(!Queue.empty());
// Don't use const reference. The subsequent pop_back() might make it
// unsafe.
WorkListUnit U = Queue.front();
Queue.pop_front();
return U;
}
};
} // namespace
std::unique_ptr<WorkList> WorkList::makeBFSBlockDFSContents() {
return std::make_unique<BFSBlockDFSContents>();
}
namespace {
class UnexploredFirstStack : public WorkList {
/// Stack of nodes known to have statements we have not traversed yet.
SmallVector<WorkListUnit, 20> StackUnexplored;
/// Stack of all other nodes.
SmallVector<WorkListUnit, 20> StackOthers;
using BlockID = unsigned;
using LocIdentifier = std::pair<BlockID, const StackFrameContext *>;
llvm::DenseSet<LocIdentifier> Reachable;
public:
bool hasWork() const override {
return !(StackUnexplored.empty() && StackOthers.empty());
}
void enqueue(const WorkListUnit &U) override {
const ExplodedNode *N = U.getNode();
auto BE = N->getLocation().getAs<BlockEntrance>();
if (!BE) {
// Assume the choice of the order of the preceding block entrance was
// correct.
StackUnexplored.push_back(U);
} else {
LocIdentifier LocId = std::make_pair(
BE->getBlock()->getBlockID(),
N->getLocationContext()->getStackFrame());
auto InsertInfo = Reachable.insert(LocId);
if (InsertInfo.second) {
StackUnexplored.push_back(U);
} else {
StackOthers.push_back(U);
}
}
MaxReachableSize.updateMax(Reachable.size());
MaxQueueSize.updateMax(StackUnexplored.size() + StackOthers.size());
}
WorkListUnit dequeue() override {
if (!StackUnexplored.empty()) {
WorkListUnit &U = StackUnexplored.back();
StackUnexplored.pop_back();
return U;
} else {
WorkListUnit &U = StackOthers.back();
StackOthers.pop_back();
return U;
}
}
};
} // namespace
std::unique_ptr<WorkList> WorkList::makeUnexploredFirst() {
return std::make_unique<UnexploredFirstStack>();
}
namespace {
class UnexploredFirstPriorityQueue : public WorkList {
using BlockID = unsigned;
using LocIdentifier = std::pair<BlockID, const StackFrameContext *>;
// How many times each location was visited.
// Is signed because we negate it later in order to have a reversed
// comparison.
using VisitedTimesMap = llvm::DenseMap<LocIdentifier, int>;
// Compare by number of times the location was visited first (negated
// to prefer less often visited locations), then by insertion time (prefer
// expanding nodes inserted sooner first).
using QueuePriority = std::pair<int, unsigned long>;
using QueueItem = std::pair<WorkListUnit, QueuePriority>;
struct ExplorationComparator {
bool operator() (const QueueItem &LHS, const QueueItem &RHS) {
return LHS.second < RHS.second;
}
};
// Number of inserted nodes, used to emulate DFS ordering in the priority
// queue when insertions are equal.
unsigned long Counter = 0;
// Number of times a current location was reached.
VisitedTimesMap NumReached;
// The top item is the largest one.
llvm::PriorityQueue<QueueItem, std::vector<QueueItem>, ExplorationComparator>
queue;
public:
bool hasWork() const override {
return !queue.empty();
}
void enqueue(const WorkListUnit &U) override {
const ExplodedNode *N = U.getNode();
unsigned NumVisited = 0;
if (auto BE = N->getLocation().getAs<BlockEntrance>()) {
LocIdentifier LocId = std::make_pair(
BE->getBlock()->getBlockID(),
N->getLocationContext()->getStackFrame());
NumVisited = NumReached[LocId]++;
}
queue.push(std::make_pair(U, std::make_pair(-NumVisited, ++Counter)));
}
WorkListUnit dequeue() override {
QueueItem U = queue.top();
queue.pop();
return U.first;
}
};
} // namespace
std::unique_ptr<WorkList> WorkList::makeUnexploredFirstPriorityQueue() {
return std::make_unique<UnexploredFirstPriorityQueue>();
}
namespace {
class UnexploredFirstPriorityLocationQueue : public WorkList {
using LocIdentifier = const CFGBlock *;
// How many times each location was visited.
// Is signed because we negate it later in order to have a reversed
// comparison.
using VisitedTimesMap = llvm::DenseMap<LocIdentifier, int>;
// Compare by number of times the location was visited first (negated
// to prefer less often visited locations), then by insertion time (prefer
// expanding nodes inserted sooner first).
using QueuePriority = std::pair<int, unsigned long>;
using QueueItem = std::pair<WorkListUnit, QueuePriority>;
struct ExplorationComparator {
bool operator() (const QueueItem &LHS, const QueueItem &RHS) {
return LHS.second < RHS.second;
}
};
// Number of inserted nodes, used to emulate DFS ordering in the priority
// queue when insertions are equal.
unsigned long Counter = 0;
// Number of times a current location was reached.
VisitedTimesMap NumReached;
// The top item is the largest one.
llvm::PriorityQueue<QueueItem, std::vector<QueueItem>, ExplorationComparator>
queue;
public:
bool hasWork() const override {
return !queue.empty();
}
void enqueue(const WorkListUnit &U) override {
const ExplodedNode *N = U.getNode();
unsigned NumVisited = 0;
if (auto BE = N->getLocation().getAs<BlockEntrance>())
NumVisited = NumReached[BE->getBlock()]++;
queue.push(std::make_pair(U, std::make_pair(-NumVisited, ++Counter)));
}
WorkListUnit dequeue() override {
QueueItem U = queue.top();
queue.pop();
return U.first;
}
};
}
std::unique_ptr<WorkList> WorkList::makeUnexploredFirstPriorityLocationQueue() {
return std::make_unique<UnexploredFirstPriorityLocationQueue>();
}