llvm-project/clang/lib/Analysis/ThreadSafety.cpp
Benjamin Kramer ea70eb30a0 Pull the Attr iteration parts out of Attr.h, so including DeclBase.h doesn't pull in all the generated Attr code.
Required to pull some functions out of line, but this shouldn't have a perf impact.
No functionality change.

llvm-svn: 169092
2012-12-01 15:09:41 +00:00

2463 lines
88 KiB
C++

//===- ThreadSafety.cpp ----------------------------------------*- C++ --*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// A intra-procedural analysis for thread safety (e.g. deadlocks and race
// conditions), based off of an annotation system.
//
// See http://clang.llvm.org/docs/LanguageExtensions.html#threadsafety for more
// information.
//
//===----------------------------------------------------------------------===//
#include "clang/Analysis/Analyses/ThreadSafety.h"
#include "clang/Analysis/Analyses/PostOrderCFGView.h"
#include "clang/Analysis/AnalysisContext.h"
#include "clang/Analysis/CFG.h"
#include "clang/Analysis/CFGStmtMap.h"
#include "clang/AST/Attr.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/StmtCXX.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/Basic/OperatorKinds.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "llvm/ADT/BitVector.h"
#include "llvm/ADT/FoldingSet.h"
#include "llvm/ADT/ImmutableMap.h"
#include "llvm/ADT/PostOrderIterator.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <utility>
#include <vector>
using namespace clang;
using namespace thread_safety;
// Key method definition
ThreadSafetyHandler::~ThreadSafetyHandler() {}
namespace {
/// SExpr implements a simple expression language that is used to store,
/// compare, and pretty-print C++ expressions. Unlike a clang Expr, a SExpr
/// does not capture surface syntax, and it does not distinguish between
/// C++ concepts, like pointers and references, that have no real semantic
/// differences. This simplicity allows SExprs to be meaningfully compared,
/// e.g.
/// (x) = x
/// (*this).foo = this->foo
/// *&a = a
///
/// Thread-safety analysis works by comparing lock expressions. Within the
/// body of a function, an expression such as "x->foo->bar.mu" will resolve to
/// a particular mutex object at run-time. Subsequent occurrences of the same
/// expression (where "same" means syntactic equality) will refer to the same
/// run-time object if three conditions hold:
/// (1) Local variables in the expression, such as "x" have not changed.
/// (2) Values on the heap that affect the expression have not changed.
/// (3) The expression involves only pure function calls.
///
/// The current implementation assumes, but does not verify, that multiple uses
/// of the same lock expression satisfies these criteria.
class SExpr {
private:
enum ExprOp {
EOP_Nop, ///< No-op
EOP_Wildcard, ///< Matches anything.
EOP_Universal, ///< Universal lock.
EOP_This, ///< This keyword.
EOP_NVar, ///< Named variable.
EOP_LVar, ///< Local variable.
EOP_Dot, ///< Field access
EOP_Call, ///< Function call
EOP_MCall, ///< Method call
EOP_Index, ///< Array index
EOP_Unary, ///< Unary operation
EOP_Binary, ///< Binary operation
EOP_Unknown ///< Catchall for everything else
};
class SExprNode {
private:
unsigned char Op; ///< Opcode of the root node
unsigned char Flags; ///< Additional opcode-specific data
unsigned short Sz; ///< Number of child nodes
const void* Data; ///< Additional opcode-specific data
public:
SExprNode(ExprOp O, unsigned F, const void* D)
: Op(static_cast<unsigned char>(O)),
Flags(static_cast<unsigned char>(F)), Sz(1), Data(D)
{ }
unsigned size() const { return Sz; }
void setSize(unsigned S) { Sz = S; }
ExprOp kind() const { return static_cast<ExprOp>(Op); }
const NamedDecl* getNamedDecl() const {
assert(Op == EOP_NVar || Op == EOP_LVar || Op == EOP_Dot);
return reinterpret_cast<const NamedDecl*>(Data);
}
const NamedDecl* getFunctionDecl() const {
assert(Op == EOP_Call || Op == EOP_MCall);
return reinterpret_cast<const NamedDecl*>(Data);
}
bool isArrow() const { return Op == EOP_Dot && Flags == 1; }
void setArrow(bool A) { Flags = A ? 1 : 0; }
unsigned arity() const {
switch (Op) {
case EOP_Nop: return 0;
case EOP_Wildcard: return 0;
case EOP_Universal: return 0;
case EOP_NVar: return 0;
case EOP_LVar: return 0;
case EOP_This: return 0;
case EOP_Dot: return 1;
case EOP_Call: return Flags+1; // First arg is function.
case EOP_MCall: return Flags+1; // First arg is implicit obj.
case EOP_Index: return 2;
case EOP_Unary: return 1;
case EOP_Binary: return 2;
case EOP_Unknown: return Flags;
}
return 0;
}
bool operator==(const SExprNode& Other) const {
// Ignore flags and size -- they don't matter.
return (Op == Other.Op &&
Data == Other.Data);
}
bool operator!=(const SExprNode& Other) const {
return !(*this == Other);
}
bool matches(const SExprNode& Other) const {
return (*this == Other) ||
(Op == EOP_Wildcard) ||
(Other.Op == EOP_Wildcard);
}
};
/// \brief Encapsulates the lexical context of a function call. The lexical
/// context includes the arguments to the call, including the implicit object
/// argument. When an attribute containing a mutex expression is attached to
/// a method, the expression may refer to formal parameters of the method.
/// Actual arguments must be substituted for formal parameters to derive
/// the appropriate mutex expression in the lexical context where the function
/// is called. PrevCtx holds the context in which the arguments themselves
/// should be evaluated; multiple calling contexts can be chained together
/// by the lock_returned attribute.
struct CallingContext {
const NamedDecl* AttrDecl; // The decl to which the attribute is attached.
Expr* SelfArg; // Implicit object argument -- e.g. 'this'
bool SelfArrow; // is Self referred to with -> or .?
unsigned NumArgs; // Number of funArgs
Expr** FunArgs; // Function arguments
CallingContext* PrevCtx; // The previous context; or 0 if none.
CallingContext(const NamedDecl *D = 0, Expr *S = 0,
unsigned N = 0, Expr **A = 0, CallingContext *P = 0)
: AttrDecl(D), SelfArg(S), SelfArrow(false),
NumArgs(N), FunArgs(A), PrevCtx(P)
{ }
};
typedef SmallVector<SExprNode, 4> NodeVector;
private:
// A SExpr is a list of SExprNodes in prefix order. The Size field allows
// the list to be traversed as a tree.
NodeVector NodeVec;
private:
unsigned makeNop() {
NodeVec.push_back(SExprNode(EOP_Nop, 0, 0));
return NodeVec.size()-1;
}
unsigned makeWildcard() {
NodeVec.push_back(SExprNode(EOP_Wildcard, 0, 0));
return NodeVec.size()-1;
}
unsigned makeUniversal() {
NodeVec.push_back(SExprNode(EOP_Universal, 0, 0));
return NodeVec.size()-1;
}
unsigned makeNamedVar(const NamedDecl *D) {
NodeVec.push_back(SExprNode(EOP_NVar, 0, D));
return NodeVec.size()-1;
}
unsigned makeLocalVar(const NamedDecl *D) {
NodeVec.push_back(SExprNode(EOP_LVar, 0, D));
return NodeVec.size()-1;
}
unsigned makeThis() {
NodeVec.push_back(SExprNode(EOP_This, 0, 0));
return NodeVec.size()-1;
}
unsigned makeDot(const NamedDecl *D, bool Arrow) {
NodeVec.push_back(SExprNode(EOP_Dot, Arrow ? 1 : 0, D));
return NodeVec.size()-1;
}
unsigned makeCall(unsigned NumArgs, const NamedDecl *D) {
NodeVec.push_back(SExprNode(EOP_Call, NumArgs, D));
return NodeVec.size()-1;
}
// Grab the very first declaration of virtual method D
const CXXMethodDecl* getFirstVirtualDecl(const CXXMethodDecl *D) {
while (true) {
D = D->getCanonicalDecl();
CXXMethodDecl::method_iterator I = D->begin_overridden_methods(),
E = D->end_overridden_methods();
if (I == E)
return D; // Method does not override anything
D = *I; // FIXME: this does not work with multiple inheritance.
}
return 0;
}
unsigned makeMCall(unsigned NumArgs, const CXXMethodDecl *D) {
NodeVec.push_back(SExprNode(EOP_MCall, NumArgs, getFirstVirtualDecl(D)));
return NodeVec.size()-1;
}
unsigned makeIndex() {
NodeVec.push_back(SExprNode(EOP_Index, 0, 0));
return NodeVec.size()-1;
}
unsigned makeUnary() {
NodeVec.push_back(SExprNode(EOP_Unary, 0, 0));
return NodeVec.size()-1;
}
unsigned makeBinary() {
NodeVec.push_back(SExprNode(EOP_Binary, 0, 0));
return NodeVec.size()-1;
}
unsigned makeUnknown(unsigned Arity) {
NodeVec.push_back(SExprNode(EOP_Unknown, Arity, 0));
return NodeVec.size()-1;
}
/// Build an SExpr from the given C++ expression.
/// Recursive function that terminates on DeclRefExpr.
/// Note: this function merely creates a SExpr; it does not check to
/// ensure that the original expression is a valid mutex expression.
///
/// NDeref returns the number of Derefence and AddressOf operations
/// preceeding the Expr; this is used to decide whether to pretty-print
/// SExprs with . or ->.
unsigned buildSExpr(Expr *Exp, CallingContext* CallCtx, int* NDeref = 0) {
if (!Exp)
return 0;
if (DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Exp)) {
NamedDecl *ND = cast<NamedDecl>(DRE->getDecl()->getCanonicalDecl());
ParmVarDecl *PV = dyn_cast_or_null<ParmVarDecl>(ND);
if (PV) {
FunctionDecl *FD =
cast<FunctionDecl>(PV->getDeclContext())->getCanonicalDecl();
unsigned i = PV->getFunctionScopeIndex();
if (CallCtx && CallCtx->FunArgs &&
FD == CallCtx->AttrDecl->getCanonicalDecl()) {
// Substitute call arguments for references to function parameters
assert(i < CallCtx->NumArgs);
return buildSExpr(CallCtx->FunArgs[i], CallCtx->PrevCtx, NDeref);
}
// Map the param back to the param of the original function declaration.
makeNamedVar(FD->getParamDecl(i));
return 1;
}
// Not a function parameter -- just store the reference.
makeNamedVar(ND);
return 1;
} else if (isa<CXXThisExpr>(Exp)) {
// Substitute parent for 'this'
if (CallCtx && CallCtx->SelfArg) {
if (!CallCtx->SelfArrow && NDeref)
// 'this' is a pointer, but self is not, so need to take address.
--(*NDeref);
return buildSExpr(CallCtx->SelfArg, CallCtx->PrevCtx, NDeref);
}
else {
makeThis();
return 1;
}
} else if (MemberExpr *ME = dyn_cast<MemberExpr>(Exp)) {
NamedDecl *ND = ME->getMemberDecl();
int ImplicitDeref = ME->isArrow() ? 1 : 0;
unsigned Root = makeDot(ND, false);
unsigned Sz = buildSExpr(ME->getBase(), CallCtx, &ImplicitDeref);
NodeVec[Root].setArrow(ImplicitDeref > 0);
NodeVec[Root].setSize(Sz + 1);
return Sz + 1;
} else if (CXXMemberCallExpr *CMCE = dyn_cast<CXXMemberCallExpr>(Exp)) {
// When calling a function with a lock_returned attribute, replace
// the function call with the expression in lock_returned.
CXXMethodDecl* MD =
cast<CXXMethodDecl>(CMCE->getMethodDecl()->getMostRecentDecl());
if (LockReturnedAttr* At = MD->getAttr<LockReturnedAttr>()) {
CallingContext LRCallCtx(CMCE->getMethodDecl());
LRCallCtx.SelfArg = CMCE->getImplicitObjectArgument();
LRCallCtx.SelfArrow =
dyn_cast<MemberExpr>(CMCE->getCallee())->isArrow();
LRCallCtx.NumArgs = CMCE->getNumArgs();
LRCallCtx.FunArgs = CMCE->getArgs();
LRCallCtx.PrevCtx = CallCtx;
return buildSExpr(At->getArg(), &LRCallCtx);
}
// Hack to treat smart pointers and iterators as pointers;
// ignore any method named get().
if (CMCE->getMethodDecl()->getNameAsString() == "get" &&
CMCE->getNumArgs() == 0) {
if (NDeref && dyn_cast<MemberExpr>(CMCE->getCallee())->isArrow())
++(*NDeref);
return buildSExpr(CMCE->getImplicitObjectArgument(), CallCtx, NDeref);
}
unsigned NumCallArgs = CMCE->getNumArgs();
unsigned Root = makeMCall(NumCallArgs, CMCE->getMethodDecl());
unsigned Sz = buildSExpr(CMCE->getImplicitObjectArgument(), CallCtx);
Expr** CallArgs = CMCE->getArgs();
for (unsigned i = 0; i < NumCallArgs; ++i) {
Sz += buildSExpr(CallArgs[i], CallCtx);
}
NodeVec[Root].setSize(Sz + 1);
return Sz + 1;
} else if (CallExpr *CE = dyn_cast<CallExpr>(Exp)) {
FunctionDecl* FD =
cast<FunctionDecl>(CE->getDirectCallee()->getMostRecentDecl());
if (LockReturnedAttr* At = FD->getAttr<LockReturnedAttr>()) {
CallingContext LRCallCtx(CE->getDirectCallee());
LRCallCtx.NumArgs = CE->getNumArgs();
LRCallCtx.FunArgs = CE->getArgs();
LRCallCtx.PrevCtx = CallCtx;
return buildSExpr(At->getArg(), &LRCallCtx);
}
// Treat smart pointers and iterators as pointers;
// ignore the * and -> operators.
if (CXXOperatorCallExpr *OE = dyn_cast<CXXOperatorCallExpr>(CE)) {
OverloadedOperatorKind k = OE->getOperator();
if (k == OO_Star) {
if (NDeref) ++(*NDeref);
return buildSExpr(OE->getArg(0), CallCtx, NDeref);
}
else if (k == OO_Arrow) {
return buildSExpr(OE->getArg(0), CallCtx, NDeref);
}
}
unsigned NumCallArgs = CE->getNumArgs();
unsigned Root = makeCall(NumCallArgs, 0);
unsigned Sz = buildSExpr(CE->getCallee(), CallCtx);
Expr** CallArgs = CE->getArgs();
for (unsigned i = 0; i < NumCallArgs; ++i) {
Sz += buildSExpr(CallArgs[i], CallCtx);
}
NodeVec[Root].setSize(Sz+1);
return Sz+1;
} else if (BinaryOperator *BOE = dyn_cast<BinaryOperator>(Exp)) {
unsigned Root = makeBinary();
unsigned Sz = buildSExpr(BOE->getLHS(), CallCtx);
Sz += buildSExpr(BOE->getRHS(), CallCtx);
NodeVec[Root].setSize(Sz);
return Sz;
} else if (UnaryOperator *UOE = dyn_cast<UnaryOperator>(Exp)) {
// Ignore & and * operators -- they're no-ops.
// However, we try to figure out whether the expression is a pointer,
// so we can use . and -> appropriately in error messages.
if (UOE->getOpcode() == UO_Deref) {
if (NDeref) ++(*NDeref);
return buildSExpr(UOE->getSubExpr(), CallCtx, NDeref);
}
if (UOE->getOpcode() == UO_AddrOf) {
if (DeclRefExpr* DRE = dyn_cast<DeclRefExpr>(UOE->getSubExpr())) {
if (DRE->getDecl()->isCXXInstanceMember()) {
// This is a pointer-to-member expression, e.g. &MyClass::mu_.
// We interpret this syntax specially, as a wildcard.
unsigned Root = makeDot(DRE->getDecl(), false);
makeWildcard();
NodeVec[Root].setSize(2);
return 2;
}
}
if (NDeref) --(*NDeref);
return buildSExpr(UOE->getSubExpr(), CallCtx, NDeref);
}
unsigned Root = makeUnary();
unsigned Sz = buildSExpr(UOE->getSubExpr(), CallCtx);
NodeVec[Root].setSize(Sz);
return Sz;
} else if (ArraySubscriptExpr *ASE = dyn_cast<ArraySubscriptExpr>(Exp)) {
unsigned Root = makeIndex();
unsigned Sz = buildSExpr(ASE->getBase(), CallCtx);
Sz += buildSExpr(ASE->getIdx(), CallCtx);
NodeVec[Root].setSize(Sz);
return Sz;
} else if (AbstractConditionalOperator *CE =
dyn_cast<AbstractConditionalOperator>(Exp)) {
unsigned Root = makeUnknown(3);
unsigned Sz = buildSExpr(CE->getCond(), CallCtx);
Sz += buildSExpr(CE->getTrueExpr(), CallCtx);
Sz += buildSExpr(CE->getFalseExpr(), CallCtx);
NodeVec[Root].setSize(Sz);
return Sz;
} else if (ChooseExpr *CE = dyn_cast<ChooseExpr>(Exp)) {
unsigned Root = makeUnknown(3);
unsigned Sz = buildSExpr(CE->getCond(), CallCtx);
Sz += buildSExpr(CE->getLHS(), CallCtx);
Sz += buildSExpr(CE->getRHS(), CallCtx);
NodeVec[Root].setSize(Sz);
return Sz;
} else if (CastExpr *CE = dyn_cast<CastExpr>(Exp)) {
return buildSExpr(CE->getSubExpr(), CallCtx, NDeref);
} else if (ParenExpr *PE = dyn_cast<ParenExpr>(Exp)) {
return buildSExpr(PE->getSubExpr(), CallCtx, NDeref);
} else if (ExprWithCleanups *EWC = dyn_cast<ExprWithCleanups>(Exp)) {
return buildSExpr(EWC->getSubExpr(), CallCtx, NDeref);
} else if (CXXBindTemporaryExpr *E = dyn_cast<CXXBindTemporaryExpr>(Exp)) {
return buildSExpr(E->getSubExpr(), CallCtx, NDeref);
} else if (isa<CharacterLiteral>(Exp) ||
isa<CXXNullPtrLiteralExpr>(Exp) ||
isa<GNUNullExpr>(Exp) ||
isa<CXXBoolLiteralExpr>(Exp) ||
isa<FloatingLiteral>(Exp) ||
isa<ImaginaryLiteral>(Exp) ||
isa<IntegerLiteral>(Exp) ||
isa<StringLiteral>(Exp) ||
isa<ObjCStringLiteral>(Exp)) {
makeNop();
return 1; // FIXME: Ignore literals for now
} else {
makeNop();
return 1; // Ignore. FIXME: mark as invalid expression?
}
}
/// \brief Construct a SExpr from an expression.
/// \param MutexExp The original mutex expression within an attribute
/// \param DeclExp An expression involving the Decl on which the attribute
/// occurs.
/// \param D The declaration to which the lock/unlock attribute is attached.
void buildSExprFromExpr(Expr *MutexExp, Expr *DeclExp, const NamedDecl *D,
VarDecl *SelfDecl = 0) {
CallingContext CallCtx(D);
if (MutexExp) {
if (StringLiteral* SLit = dyn_cast<StringLiteral>(MutexExp)) {
if (SLit->getString() == StringRef("*"))
// The "*" expr is a universal lock, which essentially turns off
// checks until it is removed from the lockset.
makeUniversal();
else
// Ignore other string literals for now.
makeNop();
return;
}
}
// If we are processing a raw attribute expression, with no substitutions.
if (DeclExp == 0) {
buildSExpr(MutexExp, 0);
return;
}
// Examine DeclExp to find SelfArg and FunArgs, which are used to substitute
// for formal parameters when we call buildMutexID later.
if (MemberExpr *ME = dyn_cast<MemberExpr>(DeclExp)) {
CallCtx.SelfArg = ME->getBase();
CallCtx.SelfArrow = ME->isArrow();
} else if (CXXMemberCallExpr *CE = dyn_cast<CXXMemberCallExpr>(DeclExp)) {
CallCtx.SelfArg = CE->getImplicitObjectArgument();
CallCtx.SelfArrow = dyn_cast<MemberExpr>(CE->getCallee())->isArrow();
CallCtx.NumArgs = CE->getNumArgs();
CallCtx.FunArgs = CE->getArgs();
} else if (CallExpr *CE = dyn_cast<CallExpr>(DeclExp)) {
CallCtx.NumArgs = CE->getNumArgs();
CallCtx.FunArgs = CE->getArgs();
} else if (CXXConstructExpr *CE = dyn_cast<CXXConstructExpr>(DeclExp)) {
CallCtx.SelfArg = 0; // Will be set below
CallCtx.NumArgs = CE->getNumArgs();
CallCtx.FunArgs = CE->getArgs();
} else if (D && isa<CXXDestructorDecl>(D)) {
// There's no such thing as a "destructor call" in the AST.
CallCtx.SelfArg = DeclExp;
}
// Hack to handle constructors, where self cannot be recovered from
// the expression.
if (SelfDecl && !CallCtx.SelfArg) {
DeclRefExpr SelfDRE(SelfDecl, false, SelfDecl->getType(), VK_LValue,
SelfDecl->getLocation());
CallCtx.SelfArg = &SelfDRE;
// If the attribute has no arguments, then assume the argument is "this".
if (MutexExp == 0)
buildSExpr(CallCtx.SelfArg, 0);
else // For most attributes.
buildSExpr(MutexExp, &CallCtx);
return;
}
// If the attribute has no arguments, then assume the argument is "this".
if (MutexExp == 0)
buildSExpr(CallCtx.SelfArg, 0);
else // For most attributes.
buildSExpr(MutexExp, &CallCtx);
}
/// \brief Get index of next sibling of node i.
unsigned getNextSibling(unsigned i) const {
return i + NodeVec[i].size();
}
public:
explicit SExpr(clang::Decl::EmptyShell e) { NodeVec.clear(); }
/// \param MutexExp The original mutex expression within an attribute
/// \param DeclExp An expression involving the Decl on which the attribute
/// occurs.
/// \param D The declaration to which the lock/unlock attribute is attached.
/// Caller must check isValid() after construction.
SExpr(Expr* MutexExp, Expr *DeclExp, const NamedDecl* D,
VarDecl *SelfDecl=0) {
buildSExprFromExpr(MutexExp, DeclExp, D, SelfDecl);
}
/// Return true if this is a valid decl sequence.
/// Caller must call this by hand after construction to handle errors.
bool isValid() const {
return !NodeVec.empty();
}
bool shouldIgnore() const {
// Nop is a mutex that we have decided to deliberately ignore.
assert(NodeVec.size() > 0 && "Invalid Mutex");
return NodeVec[0].kind() == EOP_Nop;
}
bool isUniversal() const {
assert(NodeVec.size() > 0 && "Invalid Mutex");
return NodeVec[0].kind() == EOP_Universal;
}
/// Issue a warning about an invalid lock expression
static void warnInvalidLock(ThreadSafetyHandler &Handler, Expr* MutexExp,
Expr *DeclExp, const NamedDecl* D) {
SourceLocation Loc;
if (DeclExp)
Loc = DeclExp->getExprLoc();
// FIXME: add a note about the attribute location in MutexExp or D
if (Loc.isValid())
Handler.handleInvalidLockExp(Loc);
}
bool operator==(const SExpr &other) const {
return NodeVec == other.NodeVec;
}
bool operator!=(const SExpr &other) const {
return !(*this == other);
}
bool matches(const SExpr &Other, unsigned i = 0, unsigned j = 0) const {
if (NodeVec[i].matches(Other.NodeVec[j])) {
unsigned ni = NodeVec[i].arity();
unsigned nj = Other.NodeVec[j].arity();
unsigned n = (ni < nj) ? ni : nj;
bool Result = true;
unsigned ci = i+1; // first child of i
unsigned cj = j+1; // first child of j
for (unsigned k = 0; k < n;
++k, ci=getNextSibling(ci), cj = Other.getNextSibling(cj)) {
Result = Result && matches(Other, ci, cj);
}
return Result;
}
return false;
}
// A partial match between a.mu and b.mu returns true a and b have the same
// type (and thus mu refers to the same mutex declaration), regardless of
// whether a and b are different objects or not.
bool partiallyMatches(const SExpr &Other) const {
if (NodeVec[0].kind() == EOP_Dot)
return NodeVec[0].matches(Other.NodeVec[0]);
return false;
}
/// \brief Pretty print a lock expression for use in error messages.
std::string toString(unsigned i = 0) const {
assert(isValid());
if (i >= NodeVec.size())
return "";
const SExprNode* N = &NodeVec[i];
switch (N->kind()) {
case EOP_Nop:
return "_";
case EOP_Wildcard:
return "(?)";
case EOP_Universal:
return "*";
case EOP_This:
return "this";
case EOP_NVar:
case EOP_LVar: {
return N->getNamedDecl()->getNameAsString();
}
case EOP_Dot: {
if (NodeVec[i+1].kind() == EOP_Wildcard) {
std::string S = "&";
S += N->getNamedDecl()->getQualifiedNameAsString();
return S;
}
std::string FieldName = N->getNamedDecl()->getNameAsString();
if (NodeVec[i+1].kind() == EOP_This)
return FieldName;
std::string S = toString(i+1);
if (N->isArrow())
return S + "->" + FieldName;
else
return S + "." + FieldName;
}
case EOP_Call: {
std::string S = toString(i+1) + "(";
unsigned NumArgs = N->arity()-1;
unsigned ci = getNextSibling(i+1);
for (unsigned k=0; k<NumArgs; ++k, ci = getNextSibling(ci)) {
S += toString(ci);
if (k+1 < NumArgs) S += ",";
}
S += ")";
return S;
}
case EOP_MCall: {
std::string S = "";
if (NodeVec[i+1].kind() != EOP_This)
S = toString(i+1) + ".";
if (const NamedDecl *D = N->getFunctionDecl())
S += D->getNameAsString() + "(";
else
S += "#(";
unsigned NumArgs = N->arity()-1;
unsigned ci = getNextSibling(i+1);
for (unsigned k=0; k<NumArgs; ++k, ci = getNextSibling(ci)) {
S += toString(ci);
if (k+1 < NumArgs) S += ",";
}
S += ")";
return S;
}
case EOP_Index: {
std::string S1 = toString(i+1);
std::string S2 = toString(i+1 + NodeVec[i+1].size());
return S1 + "[" + S2 + "]";
}
case EOP_Unary: {
std::string S = toString(i+1);
return "#" + S;
}
case EOP_Binary: {
std::string S1 = toString(i+1);
std::string S2 = toString(i+1 + NodeVec[i+1].size());
return "(" + S1 + "#" + S2 + ")";
}
case EOP_Unknown: {
unsigned NumChildren = N->arity();
if (NumChildren == 0)
return "(...)";
std::string S = "(";
unsigned ci = i+1;
for (unsigned j = 0; j < NumChildren; ++j, ci = getNextSibling(ci)) {
S += toString(ci);
if (j+1 < NumChildren) S += "#";
}
S += ")";
return S;
}
}
return "";
}
};
/// \brief A short list of SExprs
class MutexIDList : public SmallVector<SExpr, 3> {
public:
/// \brief Return true if the list contains the specified SExpr
/// Performs a linear search, because these lists are almost always very small.
bool contains(const SExpr& M) {
for (iterator I=begin(),E=end(); I != E; ++I)
if ((*I) == M) return true;
return false;
}
/// \brief Push M onto list, bud discard duplicates
void push_back_nodup(const SExpr& M) {
if (!contains(M)) push_back(M);
}
};
/// \brief This is a helper class that stores info about the most recent
/// accquire of a Lock.
///
/// The main body of the analysis maps MutexIDs to LockDatas.
struct LockData {
SourceLocation AcquireLoc;
/// \brief LKind stores whether a lock is held shared or exclusively.
/// Note that this analysis does not currently support either re-entrant
/// locking or lock "upgrading" and "downgrading" between exclusive and
/// shared.
///
/// FIXME: add support for re-entrant locking and lock up/downgrading
LockKind LKind;
bool Managed; // for ScopedLockable objects
SExpr UnderlyingMutex; // for ScopedLockable objects
LockData(SourceLocation AcquireLoc, LockKind LKind, bool M = false)
: AcquireLoc(AcquireLoc), LKind(LKind), Managed(M),
UnderlyingMutex(Decl::EmptyShell())
{}
LockData(SourceLocation AcquireLoc, LockKind LKind, const SExpr &Mu)
: AcquireLoc(AcquireLoc), LKind(LKind), Managed(false),
UnderlyingMutex(Mu)
{}
bool operator==(const LockData &other) const {
return AcquireLoc == other.AcquireLoc && LKind == other.LKind;
}
bool operator!=(const LockData &other) const {
return !(*this == other);
}
void Profile(llvm::FoldingSetNodeID &ID) const {
ID.AddInteger(AcquireLoc.getRawEncoding());
ID.AddInteger(LKind);
}
bool isAtLeast(LockKind LK) {
return (LK == LK_Shared) || (LKind == LK_Exclusive);
}
};
/// \brief A FactEntry stores a single fact that is known at a particular point
/// in the program execution. Currently, this is information regarding a lock
/// that is held at that point.
struct FactEntry {
SExpr MutID;
LockData LDat;
FactEntry(const SExpr& M, const LockData& L)
: MutID(M), LDat(L)
{ }
};
typedef unsigned short FactID;
/// \brief FactManager manages the memory for all facts that are created during
/// the analysis of a single routine.
class FactManager {
private:
std::vector<FactEntry> Facts;
public:
FactID newLock(const SExpr& M, const LockData& L) {
Facts.push_back(FactEntry(M,L));
return static_cast<unsigned short>(Facts.size() - 1);
}
const FactEntry& operator[](FactID F) const { return Facts[F]; }
FactEntry& operator[](FactID F) { return Facts[F]; }
};
/// \brief A FactSet is the set of facts that are known to be true at a
/// particular program point. FactSets must be small, because they are
/// frequently copied, and are thus implemented as a set of indices into a
/// table maintained by a FactManager. A typical FactSet only holds 1 or 2
/// locks, so we can get away with doing a linear search for lookup. Note
/// that a hashtable or map is inappropriate in this case, because lookups
/// may involve partial pattern matches, rather than exact matches.
class FactSet {
private:
typedef SmallVector<FactID, 4> FactVec;
FactVec FactIDs;
public:
typedef FactVec::iterator iterator;
typedef FactVec::const_iterator const_iterator;
iterator begin() { return FactIDs.begin(); }
const_iterator begin() const { return FactIDs.begin(); }
iterator end() { return FactIDs.end(); }
const_iterator end() const { return FactIDs.end(); }
bool isEmpty() const { return FactIDs.size() == 0; }
FactID addLock(FactManager& FM, const SExpr& M, const LockData& L) {
FactID F = FM.newLock(M, L);
FactIDs.push_back(F);
return F;
}
bool removeLock(FactManager& FM, const SExpr& M) {
unsigned n = FactIDs.size();
if (n == 0)
return false;
for (unsigned i = 0; i < n-1; ++i) {
if (FM[FactIDs[i]].MutID.matches(M)) {
FactIDs[i] = FactIDs[n-1];
FactIDs.pop_back();
return true;
}
}
if (FM[FactIDs[n-1]].MutID.matches(M)) {
FactIDs.pop_back();
return true;
}
return false;
}
LockData* findLock(FactManager &FM, const SExpr &M) const {
for (const_iterator I = begin(), E = end(); I != E; ++I) {
const SExpr &Exp = FM[*I].MutID;
if (Exp.matches(M))
return &FM[*I].LDat;
}
return 0;
}
LockData* findLockUniv(FactManager &FM, const SExpr &M) const {
for (const_iterator I = begin(), E = end(); I != E; ++I) {
const SExpr &Exp = FM[*I].MutID;
if (Exp.matches(M) || Exp.isUniversal())
return &FM[*I].LDat;
}
return 0;
}
FactEntry* findPartialMatch(FactManager &FM, const SExpr &M) const {
for (const_iterator I=begin(), E=end(); I != E; ++I) {
const SExpr& Exp = FM[*I].MutID;
if (Exp.partiallyMatches(M)) return &FM[*I];
}
return 0;
}
};
/// A Lockset maps each SExpr (defined above) to information about how it has
/// been locked.
typedef llvm::ImmutableMap<SExpr, LockData> Lockset;
typedef llvm::ImmutableMap<const NamedDecl*, unsigned> LocalVarContext;
class LocalVariableMap;
/// A side (entry or exit) of a CFG node.
enum CFGBlockSide { CBS_Entry, CBS_Exit };
/// CFGBlockInfo is a struct which contains all the information that is
/// maintained for each block in the CFG. See LocalVariableMap for more
/// information about the contexts.
struct CFGBlockInfo {
FactSet EntrySet; // Lockset held at entry to block
FactSet ExitSet; // Lockset held at exit from block
LocalVarContext EntryContext; // Context held at entry to block
LocalVarContext ExitContext; // Context held at exit from block
SourceLocation EntryLoc; // Location of first statement in block
SourceLocation ExitLoc; // Location of last statement in block.
unsigned EntryIndex; // Used to replay contexts later
bool Reachable; // Is this block reachable?
const FactSet &getSet(CFGBlockSide Side) const {
return Side == CBS_Entry ? EntrySet : ExitSet;
}
SourceLocation getLocation(CFGBlockSide Side) const {
return Side == CBS_Entry ? EntryLoc : ExitLoc;
}
private:
CFGBlockInfo(LocalVarContext EmptyCtx)
: EntryContext(EmptyCtx), ExitContext(EmptyCtx), Reachable(false)
{ }
public:
static CFGBlockInfo getEmptyBlockInfo(LocalVariableMap &M);
};
// A LocalVariableMap maintains a map from local variables to their currently
// valid definitions. It provides SSA-like functionality when traversing the
// CFG. Like SSA, each definition or assignment to a variable is assigned a
// unique name (an integer), which acts as the SSA name for that definition.
// The total set of names is shared among all CFG basic blocks.
// Unlike SSA, we do not rewrite expressions to replace local variables declrefs
// with their SSA-names. Instead, we compute a Context for each point in the
// code, which maps local variables to the appropriate SSA-name. This map
// changes with each assignment.
//
// The map is computed in a single pass over the CFG. Subsequent analyses can
// then query the map to find the appropriate Context for a statement, and use
// that Context to look up the definitions of variables.
class LocalVariableMap {
public:
typedef LocalVarContext Context;
/// A VarDefinition consists of an expression, representing the value of the
/// variable, along with the context in which that expression should be
/// interpreted. A reference VarDefinition does not itself contain this
/// information, but instead contains a pointer to a previous VarDefinition.
struct VarDefinition {
public:
friend class LocalVariableMap;
const NamedDecl *Dec; // The original declaration for this variable.
const Expr *Exp; // The expression for this variable, OR
unsigned Ref; // Reference to another VarDefinition
Context Ctx; // The map with which Exp should be interpreted.
bool isReference() { return !Exp; }
private:
// Create ordinary variable definition
VarDefinition(const NamedDecl *D, const Expr *E, Context C)
: Dec(D), Exp(E), Ref(0), Ctx(C)
{ }
// Create reference to previous definition
VarDefinition(const NamedDecl *D, unsigned R, Context C)
: Dec(D), Exp(0), Ref(R), Ctx(C)
{ }
};
private:
Context::Factory ContextFactory;
std::vector<VarDefinition> VarDefinitions;
std::vector<unsigned> CtxIndices;
std::vector<std::pair<Stmt*, Context> > SavedContexts;
public:
LocalVariableMap() {
// index 0 is a placeholder for undefined variables (aka phi-nodes).
VarDefinitions.push_back(VarDefinition(0, 0u, getEmptyContext()));
}
/// Look up a definition, within the given context.
const VarDefinition* lookup(const NamedDecl *D, Context Ctx) {
const unsigned *i = Ctx.lookup(D);
if (!i)
return 0;
assert(*i < VarDefinitions.size());
return &VarDefinitions[*i];
}
/// Look up the definition for D within the given context. Returns
/// NULL if the expression is not statically known. If successful, also
/// modifies Ctx to hold the context of the return Expr.
const Expr* lookupExpr(const NamedDecl *D, Context &Ctx) {
const unsigned *P = Ctx.lookup(D);
if (!P)
return 0;
unsigned i = *P;
while (i > 0) {
if (VarDefinitions[i].Exp) {
Ctx = VarDefinitions[i].Ctx;
return VarDefinitions[i].Exp;
}
i = VarDefinitions[i].Ref;
}
return 0;
}
Context getEmptyContext() { return ContextFactory.getEmptyMap(); }
/// Return the next context after processing S. This function is used by
/// clients of the class to get the appropriate context when traversing the
/// CFG. It must be called for every assignment or DeclStmt.
Context getNextContext(unsigned &CtxIndex, Stmt *S, Context C) {
if (SavedContexts[CtxIndex+1].first == S) {
CtxIndex++;
Context Result = SavedContexts[CtxIndex].second;
return Result;
}
return C;
}
void dumpVarDefinitionName(unsigned i) {
if (i == 0) {
llvm::errs() << "Undefined";
return;
}
const NamedDecl *Dec = VarDefinitions[i].Dec;
if (!Dec) {
llvm::errs() << "<<NULL>>";
return;
}
Dec->printName(llvm::errs());
llvm::errs() << "." << i << " " << ((const void*) Dec);
}
/// Dumps an ASCII representation of the variable map to llvm::errs()
void dump() {
for (unsigned i = 1, e = VarDefinitions.size(); i < e; ++i) {
const Expr *Exp = VarDefinitions[i].Exp;
unsigned Ref = VarDefinitions[i].Ref;
dumpVarDefinitionName(i);
llvm::errs() << " = ";
if (Exp) Exp->dump();
else {
dumpVarDefinitionName(Ref);
llvm::errs() << "\n";
}
}
}
/// Dumps an ASCII representation of a Context to llvm::errs()
void dumpContext(Context C) {
for (Context::iterator I = C.begin(), E = C.end(); I != E; ++I) {
const NamedDecl *D = I.getKey();
D->printName(llvm::errs());
const unsigned *i = C.lookup(D);
llvm::errs() << " -> ";
dumpVarDefinitionName(*i);
llvm::errs() << "\n";
}
}
/// Builds the variable map.
void traverseCFG(CFG *CFGraph, PostOrderCFGView *SortedGraph,
std::vector<CFGBlockInfo> &BlockInfo);
protected:
// Get the current context index
unsigned getContextIndex() { return SavedContexts.size()-1; }
// Save the current context for later replay
void saveContext(Stmt *S, Context C) {
SavedContexts.push_back(std::make_pair(S,C));
}
// Adds a new definition to the given context, and returns a new context.
// This method should be called when declaring a new variable.
Context addDefinition(const NamedDecl *D, Expr *Exp, Context Ctx) {
assert(!Ctx.contains(D));
unsigned newID = VarDefinitions.size();
Context NewCtx = ContextFactory.add(Ctx, D, newID);
VarDefinitions.push_back(VarDefinition(D, Exp, Ctx));
return NewCtx;
}
// Add a new reference to an existing definition.
Context addReference(const NamedDecl *D, unsigned i, Context Ctx) {
unsigned newID = VarDefinitions.size();
Context NewCtx = ContextFactory.add(Ctx, D, newID);
VarDefinitions.push_back(VarDefinition(D, i, Ctx));
return NewCtx;
}
// Updates a definition only if that definition is already in the map.
// This method should be called when assigning to an existing variable.
Context updateDefinition(const NamedDecl *D, Expr *Exp, Context Ctx) {
if (Ctx.contains(D)) {
unsigned newID = VarDefinitions.size();
Context NewCtx = ContextFactory.remove(Ctx, D);
NewCtx = ContextFactory.add(NewCtx, D, newID);
VarDefinitions.push_back(VarDefinition(D, Exp, Ctx));
return NewCtx;
}
return Ctx;
}
// Removes a definition from the context, but keeps the variable name
// as a valid variable. The index 0 is a placeholder for cleared definitions.
Context clearDefinition(const NamedDecl *D, Context Ctx) {
Context NewCtx = Ctx;
if (NewCtx.contains(D)) {
NewCtx = ContextFactory.remove(NewCtx, D);
NewCtx = ContextFactory.add(NewCtx, D, 0);
}
return NewCtx;
}
// Remove a definition entirely frmo the context.
Context removeDefinition(const NamedDecl *D, Context Ctx) {
Context NewCtx = Ctx;
if (NewCtx.contains(D)) {
NewCtx = ContextFactory.remove(NewCtx, D);
}
return NewCtx;
}
Context intersectContexts(Context C1, Context C2);
Context createReferenceContext(Context C);
void intersectBackEdge(Context C1, Context C2);
friend class VarMapBuilder;
};
// This has to be defined after LocalVariableMap.
CFGBlockInfo CFGBlockInfo::getEmptyBlockInfo(LocalVariableMap &M) {
return CFGBlockInfo(M.getEmptyContext());
}
/// Visitor which builds a LocalVariableMap
class VarMapBuilder : public StmtVisitor<VarMapBuilder> {
public:
LocalVariableMap* VMap;
LocalVariableMap::Context Ctx;
VarMapBuilder(LocalVariableMap *VM, LocalVariableMap::Context C)
: VMap(VM), Ctx(C) {}
void VisitDeclStmt(DeclStmt *S);
void VisitBinaryOperator(BinaryOperator *BO);
};
// Add new local variables to the variable map
void VarMapBuilder::VisitDeclStmt(DeclStmt *S) {
bool modifiedCtx = false;
DeclGroupRef DGrp = S->getDeclGroup();
for (DeclGroupRef::iterator I = DGrp.begin(), E = DGrp.end(); I != E; ++I) {
if (VarDecl *VD = dyn_cast_or_null<VarDecl>(*I)) {
Expr *E = VD->getInit();
// Add local variables with trivial type to the variable map
QualType T = VD->getType();
if (T.isTrivialType(VD->getASTContext())) {
Ctx = VMap->addDefinition(VD, E, Ctx);
modifiedCtx = true;
}
}
}
if (modifiedCtx)
VMap->saveContext(S, Ctx);
}
// Update local variable definitions in variable map
void VarMapBuilder::VisitBinaryOperator(BinaryOperator *BO) {
if (!BO->isAssignmentOp())
return;
Expr *LHSExp = BO->getLHS()->IgnoreParenCasts();
// Update the variable map and current context.
if (DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(LHSExp)) {
ValueDecl *VDec = DRE->getDecl();
if (Ctx.lookup(VDec)) {
if (BO->getOpcode() == BO_Assign)
Ctx = VMap->updateDefinition(VDec, BO->getRHS(), Ctx);
else
// FIXME -- handle compound assignment operators
Ctx = VMap->clearDefinition(VDec, Ctx);
VMap->saveContext(BO, Ctx);
}
}
}
// Computes the intersection of two contexts. The intersection is the
// set of variables which have the same definition in both contexts;
// variables with different definitions are discarded.
LocalVariableMap::Context
LocalVariableMap::intersectContexts(Context C1, Context C2) {
Context Result = C1;
for (Context::iterator I = C1.begin(), E = C1.end(); I != E; ++I) {
const NamedDecl *Dec = I.getKey();
unsigned i1 = I.getData();
const unsigned *i2 = C2.lookup(Dec);
if (!i2) // variable doesn't exist on second path
Result = removeDefinition(Dec, Result);
else if (*i2 != i1) // variable exists, but has different definition
Result = clearDefinition(Dec, Result);
}
return Result;
}
// For every variable in C, create a new variable that refers to the
// definition in C. Return a new context that contains these new variables.
// (We use this for a naive implementation of SSA on loop back-edges.)
LocalVariableMap::Context LocalVariableMap::createReferenceContext(Context C) {
Context Result = getEmptyContext();
for (Context::iterator I = C.begin(), E = C.end(); I != E; ++I) {
const NamedDecl *Dec = I.getKey();
unsigned i = I.getData();
Result = addReference(Dec, i, Result);
}
return Result;
}
// This routine also takes the intersection of C1 and C2, but it does so by
// altering the VarDefinitions. C1 must be the result of an earlier call to
// createReferenceContext.
void LocalVariableMap::intersectBackEdge(Context C1, Context C2) {
for (Context::iterator I = C1.begin(), E = C1.end(); I != E; ++I) {
const NamedDecl *Dec = I.getKey();
unsigned i1 = I.getData();
VarDefinition *VDef = &VarDefinitions[i1];
assert(VDef->isReference());
const unsigned *i2 = C2.lookup(Dec);
if (!i2 || (*i2 != i1))
VDef->Ref = 0; // Mark this variable as undefined
}
}
// Traverse the CFG in topological order, so all predecessors of a block
// (excluding back-edges) are visited before the block itself. At
// each point in the code, we calculate a Context, which holds the set of
// variable definitions which are visible at that point in execution.
// Visible variables are mapped to their definitions using an array that
// contains all definitions.
//
// At join points in the CFG, the set is computed as the intersection of
// the incoming sets along each edge, E.g.
//
// { Context | VarDefinitions }
// int x = 0; { x -> x1 | x1 = 0 }
// int y = 0; { x -> x1, y -> y1 | y1 = 0, x1 = 0 }
// if (b) x = 1; { x -> x2, y -> y1 | x2 = 1, y1 = 0, ... }
// else x = 2; { x -> x3, y -> y1 | x3 = 2, x2 = 1, ... }
// ... { y -> y1 (x is unknown) | x3 = 2, x2 = 1, ... }
//
// This is essentially a simpler and more naive version of the standard SSA
// algorithm. Those definitions that remain in the intersection are from blocks
// that strictly dominate the current block. We do not bother to insert proper
// phi nodes, because they are not used in our analysis; instead, wherever
// a phi node would be required, we simply remove that definition from the
// context (E.g. x above).
//
// The initial traversal does not capture back-edges, so those need to be
// handled on a separate pass. Whenever the first pass encounters an
// incoming back edge, it duplicates the context, creating new definitions
// that refer back to the originals. (These correspond to places where SSA
// might have to insert a phi node.) On the second pass, these definitions are
// set to NULL if the variable has changed on the back-edge (i.e. a phi
// node was actually required.) E.g.
//
// { Context | VarDefinitions }
// int x = 0, y = 0; { x -> x1, y -> y1 | y1 = 0, x1 = 0 }
// while (b) { x -> x2, y -> y1 | [1st:] x2=x1; [2nd:] x2=NULL; }
// x = x+1; { x -> x3, y -> y1 | x3 = x2 + 1, ... }
// ... { y -> y1 | x3 = 2, x2 = 1, ... }
//
void LocalVariableMap::traverseCFG(CFG *CFGraph,
PostOrderCFGView *SortedGraph,
std::vector<CFGBlockInfo> &BlockInfo) {
PostOrderCFGView::CFGBlockSet VisitedBlocks(CFGraph);
CtxIndices.resize(CFGraph->getNumBlockIDs());
for (PostOrderCFGView::iterator I = SortedGraph->begin(),
E = SortedGraph->end(); I!= E; ++I) {
const CFGBlock *CurrBlock = *I;
int CurrBlockID = CurrBlock->getBlockID();
CFGBlockInfo *CurrBlockInfo = &BlockInfo[CurrBlockID];
VisitedBlocks.insert(CurrBlock);
// Calculate the entry context for the current block
bool HasBackEdges = false;
bool CtxInit = true;
for (CFGBlock::const_pred_iterator PI = CurrBlock->pred_begin(),
PE = CurrBlock->pred_end(); PI != PE; ++PI) {
// if *PI -> CurrBlock is a back edge, so skip it
if (*PI == 0 || !VisitedBlocks.alreadySet(*PI)) {
HasBackEdges = true;
continue;
}
int PrevBlockID = (*PI)->getBlockID();
CFGBlockInfo *PrevBlockInfo = &BlockInfo[PrevBlockID];
if (CtxInit) {
CurrBlockInfo->EntryContext = PrevBlockInfo->ExitContext;
CtxInit = false;
}
else {
CurrBlockInfo->EntryContext =
intersectContexts(CurrBlockInfo->EntryContext,
PrevBlockInfo->ExitContext);
}
}
// Duplicate the context if we have back-edges, so we can call
// intersectBackEdges later.
if (HasBackEdges)
CurrBlockInfo->EntryContext =
createReferenceContext(CurrBlockInfo->EntryContext);
// Create a starting context index for the current block
saveContext(0, CurrBlockInfo->EntryContext);
CurrBlockInfo->EntryIndex = getContextIndex();
// Visit all the statements in the basic block.
VarMapBuilder VMapBuilder(this, CurrBlockInfo->EntryContext);
for (CFGBlock::const_iterator BI = CurrBlock->begin(),
BE = CurrBlock->end(); BI != BE; ++BI) {
switch (BI->getKind()) {
case CFGElement::Statement: {
const CFGStmt *CS = cast<CFGStmt>(&*BI);
VMapBuilder.Visit(const_cast<Stmt*>(CS->getStmt()));
break;
}
default:
break;
}
}
CurrBlockInfo->ExitContext = VMapBuilder.Ctx;
// Mark variables on back edges as "unknown" if they've been changed.
for (CFGBlock::const_succ_iterator SI = CurrBlock->succ_begin(),
SE = CurrBlock->succ_end(); SI != SE; ++SI) {
// if CurrBlock -> *SI is *not* a back edge
if (*SI == 0 || !VisitedBlocks.alreadySet(*SI))
continue;
CFGBlock *FirstLoopBlock = *SI;
Context LoopBegin = BlockInfo[FirstLoopBlock->getBlockID()].EntryContext;
Context LoopEnd = CurrBlockInfo->ExitContext;
intersectBackEdge(LoopBegin, LoopEnd);
}
}
// Put an extra entry at the end of the indexed context array
unsigned exitID = CFGraph->getExit().getBlockID();
saveContext(0, BlockInfo[exitID].ExitContext);
}
/// Find the appropriate source locations to use when producing diagnostics for
/// each block in the CFG.
static void findBlockLocations(CFG *CFGraph,
PostOrderCFGView *SortedGraph,
std::vector<CFGBlockInfo> &BlockInfo) {
for (PostOrderCFGView::iterator I = SortedGraph->begin(),
E = SortedGraph->end(); I!= E; ++I) {
const CFGBlock *CurrBlock = *I;
CFGBlockInfo *CurrBlockInfo = &BlockInfo[CurrBlock->getBlockID()];
// Find the source location of the last statement in the block, if the
// block is not empty.
if (const Stmt *S = CurrBlock->getTerminator()) {
CurrBlockInfo->EntryLoc = CurrBlockInfo->ExitLoc = S->getLocStart();
} else {
for (CFGBlock::const_reverse_iterator BI = CurrBlock->rbegin(),
BE = CurrBlock->rend(); BI != BE; ++BI) {
// FIXME: Handle other CFGElement kinds.
if (const CFGStmt *CS = dyn_cast<CFGStmt>(&*BI)) {
CurrBlockInfo->ExitLoc = CS->getStmt()->getLocStart();
break;
}
}
}
if (!CurrBlockInfo->ExitLoc.isInvalid()) {
// This block contains at least one statement. Find the source location
// of the first statement in the block.
for (CFGBlock::const_iterator BI = CurrBlock->begin(),
BE = CurrBlock->end(); BI != BE; ++BI) {
// FIXME: Handle other CFGElement kinds.
if (const CFGStmt *CS = dyn_cast<CFGStmt>(&*BI)) {
CurrBlockInfo->EntryLoc = CS->getStmt()->getLocStart();
break;
}
}
} else if (CurrBlock->pred_size() == 1 && *CurrBlock->pred_begin() &&
CurrBlock != &CFGraph->getExit()) {
// The block is empty, and has a single predecessor. Use its exit
// location.
CurrBlockInfo->EntryLoc = CurrBlockInfo->ExitLoc =
BlockInfo[(*CurrBlock->pred_begin())->getBlockID()].ExitLoc;
}
}
}
/// \brief Class which implements the core thread safety analysis routines.
class ThreadSafetyAnalyzer {
friend class BuildLockset;
ThreadSafetyHandler &Handler;
LocalVariableMap LocalVarMap;
FactManager FactMan;
std::vector<CFGBlockInfo> BlockInfo;
public:
ThreadSafetyAnalyzer(ThreadSafetyHandler &H) : Handler(H) {}
void addLock(FactSet &FSet, const SExpr &Mutex, const LockData &LDat);
void removeLock(FactSet &FSet, const SExpr &Mutex,
SourceLocation UnlockLoc, bool FullyRemove=false);
template <typename AttrType>
void getMutexIDs(MutexIDList &Mtxs, AttrType *Attr, Expr *Exp,
const NamedDecl *D, VarDecl *SelfDecl=0);
template <class AttrType>
void getMutexIDs(MutexIDList &Mtxs, AttrType *Attr, Expr *Exp,
const NamedDecl *D,
const CFGBlock *PredBlock, const CFGBlock *CurrBlock,
Expr *BrE, bool Neg);
const CallExpr* getTrylockCallExpr(const Stmt *Cond, LocalVarContext C,
bool &Negate);
void getEdgeLockset(FactSet &Result, const FactSet &ExitSet,
const CFGBlock* PredBlock,
const CFGBlock *CurrBlock);
void intersectAndWarn(FactSet &FSet1, const FactSet &FSet2,
SourceLocation JoinLoc,
LockErrorKind LEK1, LockErrorKind LEK2,
bool Modify=true);
void intersectAndWarn(FactSet &FSet1, const FactSet &FSet2,
SourceLocation JoinLoc, LockErrorKind LEK1,
bool Modify=true) {
intersectAndWarn(FSet1, FSet2, JoinLoc, LEK1, LEK1, Modify);
}
void runAnalysis(AnalysisDeclContext &AC);
};
/// \brief Add a new lock to the lockset, warning if the lock is already there.
/// \param Mutex -- the Mutex expression for the lock
/// \param LDat -- the LockData for the lock
void ThreadSafetyAnalyzer::addLock(FactSet &FSet, const SExpr &Mutex,
const LockData &LDat) {
// FIXME: deal with acquired before/after annotations.
// FIXME: Don't always warn when we have support for reentrant locks.
if (Mutex.shouldIgnore())
return;
if (FSet.findLock(FactMan, Mutex)) {
Handler.handleDoubleLock(Mutex.toString(), LDat.AcquireLoc);
} else {
FSet.addLock(FactMan, Mutex, LDat);
}
}
/// \brief Remove a lock from the lockset, warning if the lock is not there.
/// \param Mutex The lock expression corresponding to the lock to be removed
/// \param UnlockLoc The source location of the unlock (only used in error msg)
void ThreadSafetyAnalyzer::removeLock(FactSet &FSet,
const SExpr &Mutex,
SourceLocation UnlockLoc,
bool FullyRemove) {
if (Mutex.shouldIgnore())
return;
const LockData *LDat = FSet.findLock(FactMan, Mutex);
if (!LDat) {
Handler.handleUnmatchedUnlock(Mutex.toString(), UnlockLoc);
return;
}
if (LDat->UnderlyingMutex.isValid()) {
// This is scoped lockable object, which manages the real mutex.
if (FullyRemove) {
// We're destroying the managing object.
// Remove the underlying mutex if it exists; but don't warn.
if (FSet.findLock(FactMan, LDat->UnderlyingMutex))
FSet.removeLock(FactMan, LDat->UnderlyingMutex);
} else {
// We're releasing the underlying mutex, but not destroying the
// managing object. Warn on dual release.
if (!FSet.findLock(FactMan, LDat->UnderlyingMutex)) {
Handler.handleUnmatchedUnlock(LDat->UnderlyingMutex.toString(),
UnlockLoc);
}
FSet.removeLock(FactMan, LDat->UnderlyingMutex);
return;
}
}
FSet.removeLock(FactMan, Mutex);
}
/// \brief Extract the list of mutexIDs from the attribute on an expression,
/// and push them onto Mtxs, discarding any duplicates.
template <typename AttrType>
void ThreadSafetyAnalyzer::getMutexIDs(MutexIDList &Mtxs, AttrType *Attr,
Expr *Exp, const NamedDecl *D,
VarDecl *SelfDecl) {
typedef typename AttrType::args_iterator iterator_type;
if (Attr->args_size() == 0) {
// The mutex held is the "this" object.
SExpr Mu(0, Exp, D, SelfDecl);
if (!Mu.isValid())
SExpr::warnInvalidLock(Handler, 0, Exp, D);
else
Mtxs.push_back_nodup(Mu);
return;
}
for (iterator_type I=Attr->args_begin(), E=Attr->args_end(); I != E; ++I) {
SExpr Mu(*I, Exp, D, SelfDecl);
if (!Mu.isValid())
SExpr::warnInvalidLock(Handler, *I, Exp, D);
else
Mtxs.push_back_nodup(Mu);
}
}
/// \brief Extract the list of mutexIDs from a trylock attribute. If the
/// trylock applies to the given edge, then push them onto Mtxs, discarding
/// any duplicates.
template <class AttrType>
void ThreadSafetyAnalyzer::getMutexIDs(MutexIDList &Mtxs, AttrType *Attr,
Expr *Exp, const NamedDecl *D,
const CFGBlock *PredBlock,
const CFGBlock *CurrBlock,
Expr *BrE, bool Neg) {
// Find out which branch has the lock
bool branch = 0;
if (CXXBoolLiteralExpr *BLE = dyn_cast_or_null<CXXBoolLiteralExpr>(BrE)) {
branch = BLE->getValue();
}
else if (IntegerLiteral *ILE = dyn_cast_or_null<IntegerLiteral>(BrE)) {
branch = ILE->getValue().getBoolValue();
}
int branchnum = branch ? 0 : 1;
if (Neg) branchnum = !branchnum;
// If we've taken the trylock branch, then add the lock
int i = 0;
for (CFGBlock::const_succ_iterator SI = PredBlock->succ_begin(),
SE = PredBlock->succ_end(); SI != SE && i < 2; ++SI, ++i) {
if (*SI == CurrBlock && i == branchnum) {
getMutexIDs(Mtxs, Attr, Exp, D);
}
}
}
bool getStaticBooleanValue(Expr* E, bool& TCond) {
if (isa<CXXNullPtrLiteralExpr>(E) || isa<GNUNullExpr>(E)) {
TCond = false;
return true;
} else if (CXXBoolLiteralExpr *BLE = dyn_cast<CXXBoolLiteralExpr>(E)) {
TCond = BLE->getValue();
return true;
} else if (IntegerLiteral *ILE = dyn_cast<IntegerLiteral>(E)) {
TCond = ILE->getValue().getBoolValue();
return true;
} else if (ImplicitCastExpr *CE = dyn_cast<ImplicitCastExpr>(E)) {
return getStaticBooleanValue(CE->getSubExpr(), TCond);
}
return false;
}
// If Cond can be traced back to a function call, return the call expression.
// The negate variable should be called with false, and will be set to true
// if the function call is negated, e.g. if (!mu.tryLock(...))
const CallExpr* ThreadSafetyAnalyzer::getTrylockCallExpr(const Stmt *Cond,
LocalVarContext C,
bool &Negate) {
if (!Cond)
return 0;
if (const CallExpr *CallExp = dyn_cast<CallExpr>(Cond)) {
return CallExp;
}
else if (const ParenExpr *PE = dyn_cast<ParenExpr>(Cond)) {
return getTrylockCallExpr(PE->getSubExpr(), C, Negate);
}
else if (const ImplicitCastExpr *CE = dyn_cast<ImplicitCastExpr>(Cond)) {
return getTrylockCallExpr(CE->getSubExpr(), C, Negate);
}
else if (const ExprWithCleanups* EWC = dyn_cast<ExprWithCleanups>(Cond)) {
return getTrylockCallExpr(EWC->getSubExpr(), C, Negate);
}
else if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Cond)) {
const Expr *E = LocalVarMap.lookupExpr(DRE->getDecl(), C);
return getTrylockCallExpr(E, C, Negate);
}
else if (const UnaryOperator *UOP = dyn_cast<UnaryOperator>(Cond)) {
if (UOP->getOpcode() == UO_LNot) {
Negate = !Negate;
return getTrylockCallExpr(UOP->getSubExpr(), C, Negate);
}
return 0;
}
else if (const BinaryOperator *BOP = dyn_cast<BinaryOperator>(Cond)) {
if (BOP->getOpcode() == BO_EQ || BOP->getOpcode() == BO_NE) {
if (BOP->getOpcode() == BO_NE)
Negate = !Negate;
bool TCond = false;
if (getStaticBooleanValue(BOP->getRHS(), TCond)) {
if (!TCond) Negate = !Negate;
return getTrylockCallExpr(BOP->getLHS(), C, Negate);
}
else if (getStaticBooleanValue(BOP->getLHS(), TCond)) {
if (!TCond) Negate = !Negate;
return getTrylockCallExpr(BOP->getRHS(), C, Negate);
}
return 0;
}
return 0;
}
// FIXME -- handle && and || as well.
return 0;
}
/// \brief Find the lockset that holds on the edge between PredBlock
/// and CurrBlock. The edge set is the exit set of PredBlock (passed
/// as the ExitSet parameter) plus any trylocks, which are conditionally held.
void ThreadSafetyAnalyzer::getEdgeLockset(FactSet& Result,
const FactSet &ExitSet,
const CFGBlock *PredBlock,
const CFGBlock *CurrBlock) {
Result = ExitSet;
if (!PredBlock->getTerminatorCondition())
return;
bool Negate = false;
const Stmt *Cond = PredBlock->getTerminatorCondition();
const CFGBlockInfo *PredBlockInfo = &BlockInfo[PredBlock->getBlockID()];
const LocalVarContext &LVarCtx = PredBlockInfo->ExitContext;
CallExpr *Exp =
const_cast<CallExpr*>(getTrylockCallExpr(Cond, LVarCtx, Negate));
if (!Exp)
return;
NamedDecl *FunDecl = dyn_cast_or_null<NamedDecl>(Exp->getCalleeDecl());
if(!FunDecl || !FunDecl->hasAttrs())
return;
MutexIDList ExclusiveLocksToAdd;
MutexIDList SharedLocksToAdd;
// If the condition is a call to a Trylock function, then grab the attributes
AttrVec &ArgAttrs = FunDecl->getAttrs();
for (unsigned i = 0; i < ArgAttrs.size(); ++i) {
Attr *Attr = ArgAttrs[i];
switch (Attr->getKind()) {
case attr::ExclusiveTrylockFunction: {
ExclusiveTrylockFunctionAttr *A =
cast<ExclusiveTrylockFunctionAttr>(Attr);
getMutexIDs(ExclusiveLocksToAdd, A, Exp, FunDecl,
PredBlock, CurrBlock, A->getSuccessValue(), Negate);
break;
}
case attr::SharedTrylockFunction: {
SharedTrylockFunctionAttr *A =
cast<SharedTrylockFunctionAttr>(Attr);
getMutexIDs(SharedLocksToAdd, A, Exp, FunDecl,
PredBlock, CurrBlock, A->getSuccessValue(), Negate);
break;
}
default:
break;
}
}
// Add and remove locks.
SourceLocation Loc = Exp->getExprLoc();
for (unsigned i=0,n=ExclusiveLocksToAdd.size(); i<n; ++i) {
addLock(Result, ExclusiveLocksToAdd[i],
LockData(Loc, LK_Exclusive));
}
for (unsigned i=0,n=SharedLocksToAdd.size(); i<n; ++i) {
addLock(Result, SharedLocksToAdd[i],
LockData(Loc, LK_Shared));
}
}
/// \brief We use this class to visit different types of expressions in
/// CFGBlocks, and build up the lockset.
/// An expression may cause us to add or remove locks from the lockset, or else
/// output error messages related to missing locks.
/// FIXME: In future, we may be able to not inherit from a visitor.
class BuildLockset : public StmtVisitor<BuildLockset> {
friend class ThreadSafetyAnalyzer;
ThreadSafetyAnalyzer *Analyzer;
FactSet FSet;
LocalVariableMap::Context LVarCtx;
unsigned CtxIndex;
// Helper functions
const ValueDecl *getValueDecl(Expr *Exp);
void warnIfMutexNotHeld(const NamedDecl *D, Expr *Exp, AccessKind AK,
Expr *MutexExp, ProtectedOperationKind POK);
void warnIfMutexHeld(const NamedDecl *D, Expr *Exp, Expr *MutexExp);
void checkAccess(Expr *Exp, AccessKind AK);
void checkDereference(Expr *Exp, AccessKind AK);
void handleCall(Expr *Exp, const NamedDecl *D, VarDecl *VD = 0);
public:
BuildLockset(ThreadSafetyAnalyzer *Anlzr, CFGBlockInfo &Info)
: StmtVisitor<BuildLockset>(),
Analyzer(Anlzr),
FSet(Info.EntrySet),
LVarCtx(Info.EntryContext),
CtxIndex(Info.EntryIndex)
{}
void VisitUnaryOperator(UnaryOperator *UO);
void VisitBinaryOperator(BinaryOperator *BO);
void VisitCastExpr(CastExpr *CE);
void VisitCallExpr(CallExpr *Exp);
void VisitCXXConstructExpr(CXXConstructExpr *Exp);
void VisitDeclStmt(DeclStmt *S);
};
/// \brief Gets the value decl pointer from DeclRefExprs or MemberExprs
const ValueDecl *BuildLockset::getValueDecl(Expr *Exp) {
if (const DeclRefExpr *DR = dyn_cast<DeclRefExpr>(Exp))
return DR->getDecl();
if (const MemberExpr *ME = dyn_cast<MemberExpr>(Exp))
return ME->getMemberDecl();
return 0;
}
/// \brief Warn if the LSet does not contain a lock sufficient to protect access
/// of at least the passed in AccessKind.
void BuildLockset::warnIfMutexNotHeld(const NamedDecl *D, Expr *Exp,
AccessKind AK, Expr *MutexExp,
ProtectedOperationKind POK) {
LockKind LK = getLockKindFromAccessKind(AK);
SExpr Mutex(MutexExp, Exp, D);
if (!Mutex.isValid()) {
SExpr::warnInvalidLock(Analyzer->Handler, MutexExp, Exp, D);
return;
} else if (Mutex.shouldIgnore()) {
return;
}
LockData* LDat = FSet.findLockUniv(Analyzer->FactMan, Mutex);
bool NoError = true;
if (!LDat) {
// No exact match found. Look for a partial match.
FactEntry* FEntry = FSet.findPartialMatch(Analyzer->FactMan, Mutex);
if (FEntry) {
// Warn that there's no precise match.
LDat = &FEntry->LDat;
std::string PartMatchStr = FEntry->MutID.toString();
StringRef PartMatchName(PartMatchStr);
Analyzer->Handler.handleMutexNotHeld(D, POK, Mutex.toString(), LK,
Exp->getExprLoc(), &PartMatchName);
} else {
// Warn that there's no match at all.
Analyzer->Handler.handleMutexNotHeld(D, POK, Mutex.toString(), LK,
Exp->getExprLoc());
}
NoError = false;
}
// Make sure the mutex we found is the right kind.
if (NoError && LDat && !LDat->isAtLeast(LK))
Analyzer->Handler.handleMutexNotHeld(D, POK, Mutex.toString(), LK,
Exp->getExprLoc());
}
/// \brief Warn if the LSet contains the given lock.
void BuildLockset::warnIfMutexHeld(const NamedDecl *D, Expr* Exp,
Expr *MutexExp) {
SExpr Mutex(MutexExp, Exp, D);
if (!Mutex.isValid()) {
SExpr::warnInvalidLock(Analyzer->Handler, MutexExp, Exp, D);
return;
}
LockData* LDat = FSet.findLock(Analyzer->FactMan, Mutex);
if (LDat) {
std::string DeclName = D->getNameAsString();
StringRef DeclNameSR (DeclName);
Analyzer->Handler.handleFunExcludesLock(DeclNameSR, Mutex.toString(),
Exp->getExprLoc());
}
}
/// \brief This method identifies variable dereferences and checks pt_guarded_by
/// and pt_guarded_var annotations. Note that we only check these annotations
/// at the time a pointer is dereferenced.
/// FIXME: We need to check for other types of pointer dereferences
/// (e.g. [], ->) and deal with them here.
/// \param Exp An expression that has been read or written.
void BuildLockset::checkDereference(Expr *Exp, AccessKind AK) {
UnaryOperator *UO = dyn_cast<UnaryOperator>(Exp);
if (!UO || UO->getOpcode() != clang::UO_Deref)
return;
Exp = UO->getSubExpr()->IgnoreParenCasts();
const ValueDecl *D = getValueDecl(Exp);
if(!D || !D->hasAttrs())
return;
if (D->getAttr<PtGuardedVarAttr>() && FSet.isEmpty())
Analyzer->Handler.handleNoMutexHeld(D, POK_VarDereference, AK,
Exp->getExprLoc());
const AttrVec &ArgAttrs = D->getAttrs();
for(unsigned i = 0, Size = ArgAttrs.size(); i < Size; ++i)
if (PtGuardedByAttr *PGBAttr = dyn_cast<PtGuardedByAttr>(ArgAttrs[i]))
warnIfMutexNotHeld(D, Exp, AK, PGBAttr->getArg(), POK_VarDereference);
}
/// \brief Checks guarded_by and guarded_var attributes.
/// Whenever we identify an access (read or write) of a DeclRefExpr or
/// MemberExpr, we need to check whether there are any guarded_by or
/// guarded_var attributes, and make sure we hold the appropriate mutexes.
void BuildLockset::checkAccess(Expr *Exp, AccessKind AK) {
const ValueDecl *D = getValueDecl(Exp);
if(!D || !D->hasAttrs())
return;
if (D->getAttr<GuardedVarAttr>() && FSet.isEmpty())
Analyzer->Handler.handleNoMutexHeld(D, POK_VarAccess, AK,
Exp->getExprLoc());
const AttrVec &ArgAttrs = D->getAttrs();
for(unsigned i = 0, Size = ArgAttrs.size(); i < Size; ++i)
if (GuardedByAttr *GBAttr = dyn_cast<GuardedByAttr>(ArgAttrs[i]))
warnIfMutexNotHeld(D, Exp, AK, GBAttr->getArg(), POK_VarAccess);
}
/// \brief Process a function call, method call, constructor call,
/// or destructor call. This involves looking at the attributes on the
/// corresponding function/method/constructor/destructor, issuing warnings,
/// and updating the locksets accordingly.
///
/// FIXME: For classes annotated with one of the guarded annotations, we need
/// to treat const method calls as reads and non-const method calls as writes,
/// and check that the appropriate locks are held. Non-const method calls with
/// the same signature as const method calls can be also treated as reads.
///
void BuildLockset::handleCall(Expr *Exp, const NamedDecl *D, VarDecl *VD) {
const AttrVec &ArgAttrs = D->getAttrs();
MutexIDList ExclusiveLocksToAdd;
MutexIDList SharedLocksToAdd;
MutexIDList LocksToRemove;
for(unsigned i = 0; i < ArgAttrs.size(); ++i) {
Attr *At = const_cast<Attr*>(ArgAttrs[i]);
switch (At->getKind()) {
// When we encounter an exclusive lock function, we need to add the lock
// to our lockset with kind exclusive.
case attr::ExclusiveLockFunction: {
ExclusiveLockFunctionAttr *A = cast<ExclusiveLockFunctionAttr>(At);
Analyzer->getMutexIDs(ExclusiveLocksToAdd, A, Exp, D, VD);
break;
}
// When we encounter a shared lock function, we need to add the lock
// to our lockset with kind shared.
case attr::SharedLockFunction: {
SharedLockFunctionAttr *A = cast<SharedLockFunctionAttr>(At);
Analyzer->getMutexIDs(SharedLocksToAdd, A, Exp, D, VD);
break;
}
// When we encounter an unlock function, we need to remove unlocked
// mutexes from the lockset, and flag a warning if they are not there.
case attr::UnlockFunction: {
UnlockFunctionAttr *A = cast<UnlockFunctionAttr>(At);
Analyzer->getMutexIDs(LocksToRemove, A, Exp, D, VD);
break;
}
case attr::ExclusiveLocksRequired: {
ExclusiveLocksRequiredAttr *A = cast<ExclusiveLocksRequiredAttr>(At);
for (ExclusiveLocksRequiredAttr::args_iterator
I = A->args_begin(), E = A->args_end(); I != E; ++I)
warnIfMutexNotHeld(D, Exp, AK_Written, *I, POK_FunctionCall);
break;
}
case attr::SharedLocksRequired: {
SharedLocksRequiredAttr *A = cast<SharedLocksRequiredAttr>(At);
for (SharedLocksRequiredAttr::args_iterator I = A->args_begin(),
E = A->args_end(); I != E; ++I)
warnIfMutexNotHeld(D, Exp, AK_Read, *I, POK_FunctionCall);
break;
}
case attr::LocksExcluded: {
LocksExcludedAttr *A = cast<LocksExcludedAttr>(At);
for (LocksExcludedAttr::args_iterator I = A->args_begin(),
E = A->args_end(); I != E; ++I) {
warnIfMutexHeld(D, Exp, *I);
}
break;
}
// Ignore other (non thread-safety) attributes
default:
break;
}
}
// Figure out if we're calling the constructor of scoped lockable class
bool isScopedVar = false;
if (VD) {
if (const CXXConstructorDecl *CD = dyn_cast<const CXXConstructorDecl>(D)) {
const CXXRecordDecl* PD = CD->getParent();
if (PD && PD->getAttr<ScopedLockableAttr>())
isScopedVar = true;
}
}
// Add locks.
SourceLocation Loc = Exp->getExprLoc();
for (unsigned i=0,n=ExclusiveLocksToAdd.size(); i<n; ++i) {
Analyzer->addLock(FSet, ExclusiveLocksToAdd[i],
LockData(Loc, LK_Exclusive, isScopedVar));
}
for (unsigned i=0,n=SharedLocksToAdd.size(); i<n; ++i) {
Analyzer->addLock(FSet, SharedLocksToAdd[i],
LockData(Loc, LK_Shared, isScopedVar));
}
// Add the managing object as a dummy mutex, mapped to the underlying mutex.
// FIXME -- this doesn't work if we acquire multiple locks.
if (isScopedVar) {
SourceLocation MLoc = VD->getLocation();
DeclRefExpr DRE(VD, false, VD->getType(), VK_LValue, VD->getLocation());
SExpr SMutex(&DRE, 0, 0);
for (unsigned i=0,n=ExclusiveLocksToAdd.size(); i<n; ++i) {
Analyzer->addLock(FSet, SMutex, LockData(MLoc, LK_Exclusive,
ExclusiveLocksToAdd[i]));
}
for (unsigned i=0,n=SharedLocksToAdd.size(); i<n; ++i) {
Analyzer->addLock(FSet, SMutex, LockData(MLoc, LK_Shared,
SharedLocksToAdd[i]));
}
}
// Remove locks.
// FIXME -- should only fully remove if the attribute refers to 'this'.
bool Dtor = isa<CXXDestructorDecl>(D);
for (unsigned i=0,n=LocksToRemove.size(); i<n; ++i) {
Analyzer->removeLock(FSet, LocksToRemove[i], Loc, Dtor);
}
}
/// \brief For unary operations which read and write a variable, we need to
/// check whether we hold any required mutexes. Reads are checked in
/// VisitCastExpr.
void BuildLockset::VisitUnaryOperator(UnaryOperator *UO) {
switch (UO->getOpcode()) {
case clang::UO_PostDec:
case clang::UO_PostInc:
case clang::UO_PreDec:
case clang::UO_PreInc: {
Expr *SubExp = UO->getSubExpr()->IgnoreParenCasts();
checkAccess(SubExp, AK_Written);
checkDereference(SubExp, AK_Written);
break;
}
default:
break;
}
}
/// For binary operations which assign to a variable (writes), we need to check
/// whether we hold any required mutexes.
/// FIXME: Deal with non-primitive types.
void BuildLockset::VisitBinaryOperator(BinaryOperator *BO) {
if (!BO->isAssignmentOp())
return;
// adjust the context
LVarCtx = Analyzer->LocalVarMap.getNextContext(CtxIndex, BO, LVarCtx);
Expr *LHSExp = BO->getLHS()->IgnoreParenCasts();
checkAccess(LHSExp, AK_Written);
checkDereference(LHSExp, AK_Written);
}
/// Whenever we do an LValue to Rvalue cast, we are reading a variable and
/// need to ensure we hold any required mutexes.
/// FIXME: Deal with non-primitive types.
void BuildLockset::VisitCastExpr(CastExpr *CE) {
if (CE->getCastKind() != CK_LValueToRValue)
return;
Expr *SubExp = CE->getSubExpr()->IgnoreParenCasts();
checkAccess(SubExp, AK_Read);
checkDereference(SubExp, AK_Read);
}
void BuildLockset::VisitCallExpr(CallExpr *Exp) {
NamedDecl *D = dyn_cast_or_null<NamedDecl>(Exp->getCalleeDecl());
if(!D || !D->hasAttrs())
return;
handleCall(Exp, D);
}
void BuildLockset::VisitCXXConstructExpr(CXXConstructExpr *Exp) {
// FIXME -- only handles constructors in DeclStmt below.
}
void BuildLockset::VisitDeclStmt(DeclStmt *S) {
// adjust the context
LVarCtx = Analyzer->LocalVarMap.getNextContext(CtxIndex, S, LVarCtx);
DeclGroupRef DGrp = S->getDeclGroup();
for (DeclGroupRef::iterator I = DGrp.begin(), E = DGrp.end(); I != E; ++I) {
Decl *D = *I;
if (VarDecl *VD = dyn_cast_or_null<VarDecl>(D)) {
Expr *E = VD->getInit();
// handle constructors that involve temporaries
if (ExprWithCleanups *EWC = dyn_cast_or_null<ExprWithCleanups>(E))
E = EWC->getSubExpr();
if (CXXConstructExpr *CE = dyn_cast_or_null<CXXConstructExpr>(E)) {
NamedDecl *CtorD = dyn_cast_or_null<NamedDecl>(CE->getConstructor());
if (!CtorD || !CtorD->hasAttrs())
return;
handleCall(CE, CtorD, VD);
}
}
}
}
/// \brief Compute the intersection of two locksets and issue warnings for any
/// locks in the symmetric difference.
///
/// This function is used at a merge point in the CFG when comparing the lockset
/// of each branch being merged. For example, given the following sequence:
/// A; if () then B; else C; D; we need to check that the lockset after B and C
/// are the same. In the event of a difference, we use the intersection of these
/// two locksets at the start of D.
///
/// \param FSet1 The first lockset.
/// \param FSet2 The second lockset.
/// \param JoinLoc The location of the join point for error reporting
/// \param LEK1 The error message to report if a mutex is missing from LSet1
/// \param LEK2 The error message to report if a mutex is missing from Lset2
void ThreadSafetyAnalyzer::intersectAndWarn(FactSet &FSet1,
const FactSet &FSet2,
SourceLocation JoinLoc,
LockErrorKind LEK1,
LockErrorKind LEK2,
bool Modify) {
FactSet FSet1Orig = FSet1;
for (FactSet::const_iterator I = FSet2.begin(), E = FSet2.end();
I != E; ++I) {
const SExpr &FSet2Mutex = FactMan[*I].MutID;
const LockData &LDat2 = FactMan[*I].LDat;
if (const LockData *LDat1 = FSet1.findLock(FactMan, FSet2Mutex)) {
if (LDat1->LKind != LDat2.LKind) {
Handler.handleExclusiveAndShared(FSet2Mutex.toString(),
LDat2.AcquireLoc,
LDat1->AcquireLoc);
if (Modify && LDat1->LKind != LK_Exclusive) {
FSet1.removeLock(FactMan, FSet2Mutex);
FSet1.addLock(FactMan, FSet2Mutex, LDat2);
}
}
} else {
if (LDat2.UnderlyingMutex.isValid()) {
if (FSet2.findLock(FactMan, LDat2.UnderlyingMutex)) {
// If this is a scoped lock that manages another mutex, and if the
// underlying mutex is still held, then warn about the underlying
// mutex.
Handler.handleMutexHeldEndOfScope(LDat2.UnderlyingMutex.toString(),
LDat2.AcquireLoc,
JoinLoc, LEK1);
}
}
else if (!LDat2.Managed && !FSet2Mutex.isUniversal())
Handler.handleMutexHeldEndOfScope(FSet2Mutex.toString(),
LDat2.AcquireLoc,
JoinLoc, LEK1);
}
}
for (FactSet::const_iterator I = FSet1.begin(), E = FSet1.end();
I != E; ++I) {
const SExpr &FSet1Mutex = FactMan[*I].MutID;
const LockData &LDat1 = FactMan[*I].LDat;
if (!FSet2.findLock(FactMan, FSet1Mutex)) {
if (LDat1.UnderlyingMutex.isValid()) {
if (FSet1Orig.findLock(FactMan, LDat1.UnderlyingMutex)) {
// If this is a scoped lock that manages another mutex, and if the
// underlying mutex is still held, then warn about the underlying
// mutex.
Handler.handleMutexHeldEndOfScope(LDat1.UnderlyingMutex.toString(),
LDat1.AcquireLoc,
JoinLoc, LEK1);
}
}
else if (!LDat1.Managed && !FSet1Mutex.isUniversal())
Handler.handleMutexHeldEndOfScope(FSet1Mutex.toString(),
LDat1.AcquireLoc,
JoinLoc, LEK2);
if (Modify)
FSet1.removeLock(FactMan, FSet1Mutex);
}
}
}
/// \brief Check a function's CFG for thread-safety violations.
///
/// We traverse the blocks in the CFG, compute the set of mutexes that are held
/// at the end of each block, and issue warnings for thread safety violations.
/// Each block in the CFG is traversed exactly once.
void ThreadSafetyAnalyzer::runAnalysis(AnalysisDeclContext &AC) {
CFG *CFGraph = AC.getCFG();
if (!CFGraph) return;
const NamedDecl *D = dyn_cast_or_null<NamedDecl>(AC.getDecl());
// AC.dumpCFG(true);
if (!D)
return; // Ignore anonymous functions for now.
if (D->getAttr<NoThreadSafetyAnalysisAttr>())
return;
// FIXME: Do something a bit more intelligent inside constructor and
// destructor code. Constructors and destructors must assume unique access
// to 'this', so checks on member variable access is disabled, but we should
// still enable checks on other objects.
if (isa<CXXConstructorDecl>(D))
return; // Don't check inside constructors.
if (isa<CXXDestructorDecl>(D))
return; // Don't check inside destructors.
BlockInfo.resize(CFGraph->getNumBlockIDs(),
CFGBlockInfo::getEmptyBlockInfo(LocalVarMap));
// We need to explore the CFG via a "topological" ordering.
// That way, we will be guaranteed to have information about required
// predecessor locksets when exploring a new block.
PostOrderCFGView *SortedGraph = AC.getAnalysis<PostOrderCFGView>();
PostOrderCFGView::CFGBlockSet VisitedBlocks(CFGraph);
// Mark entry block as reachable
BlockInfo[CFGraph->getEntry().getBlockID()].Reachable = true;
// Compute SSA names for local variables
LocalVarMap.traverseCFG(CFGraph, SortedGraph, BlockInfo);
// Fill in source locations for all CFGBlocks.
findBlockLocations(CFGraph, SortedGraph, BlockInfo);
// Add locks from exclusive_locks_required and shared_locks_required
// to initial lockset. Also turn off checking for lock and unlock functions.
// FIXME: is there a more intelligent way to check lock/unlock functions?
if (!SortedGraph->empty() && D->hasAttrs()) {
const CFGBlock *FirstBlock = *SortedGraph->begin();
FactSet &InitialLockset = BlockInfo[FirstBlock->getBlockID()].EntrySet;
const AttrVec &ArgAttrs = D->getAttrs();
MutexIDList ExclusiveLocksToAdd;
MutexIDList SharedLocksToAdd;
SourceLocation Loc = D->getLocation();
for (unsigned i = 0; i < ArgAttrs.size(); ++i) {
Attr *Attr = ArgAttrs[i];
Loc = Attr->getLocation();
if (ExclusiveLocksRequiredAttr *A
= dyn_cast<ExclusiveLocksRequiredAttr>(Attr)) {
getMutexIDs(ExclusiveLocksToAdd, A, (Expr*) 0, D);
} else if (SharedLocksRequiredAttr *A
= dyn_cast<SharedLocksRequiredAttr>(Attr)) {
getMutexIDs(SharedLocksToAdd, A, (Expr*) 0, D);
} else if (isa<UnlockFunctionAttr>(Attr)) {
// Don't try to check unlock functions for now
return;
} else if (isa<ExclusiveLockFunctionAttr>(Attr)) {
// Don't try to check lock functions for now
return;
} else if (isa<SharedLockFunctionAttr>(Attr)) {
// Don't try to check lock functions for now
return;
} else if (isa<ExclusiveTrylockFunctionAttr>(Attr)) {
// Don't try to check trylock functions for now
return;
} else if (isa<SharedTrylockFunctionAttr>(Attr)) {
// Don't try to check trylock functions for now
return;
}
}
// FIXME -- Loc can be wrong here.
for (unsigned i=0,n=ExclusiveLocksToAdd.size(); i<n; ++i) {
addLock(InitialLockset, ExclusiveLocksToAdd[i],
LockData(Loc, LK_Exclusive));
}
for (unsigned i=0,n=SharedLocksToAdd.size(); i<n; ++i) {
addLock(InitialLockset, SharedLocksToAdd[i],
LockData(Loc, LK_Shared));
}
}
for (PostOrderCFGView::iterator I = SortedGraph->begin(),
E = SortedGraph->end(); I!= E; ++I) {
const CFGBlock *CurrBlock = *I;
int CurrBlockID = CurrBlock->getBlockID();
CFGBlockInfo *CurrBlockInfo = &BlockInfo[CurrBlockID];
// Use the default initial lockset in case there are no predecessors.
VisitedBlocks.insert(CurrBlock);
// Iterate through the predecessor blocks and warn if the lockset for all
// predecessors is not the same. We take the entry lockset of the current
// block to be the intersection of all previous locksets.
// FIXME: By keeping the intersection, we may output more errors in future
// for a lock which is not in the intersection, but was in the union. We
// may want to also keep the union in future. As an example, let's say
// the intersection contains Mutex L, and the union contains L and M.
// Later we unlock M. At this point, we would output an error because we
// never locked M; although the real error is probably that we forgot to
// lock M on all code paths. Conversely, let's say that later we lock M.
// In this case, we should compare against the intersection instead of the
// union because the real error is probably that we forgot to unlock M on
// all code paths.
bool LocksetInitialized = false;
llvm::SmallVector<CFGBlock*, 8> SpecialBlocks;
for (CFGBlock::const_pred_iterator PI = CurrBlock->pred_begin(),
PE = CurrBlock->pred_end(); PI != PE; ++PI) {
// if *PI -> CurrBlock is a back edge
if (*PI == 0 || !VisitedBlocks.alreadySet(*PI))
continue;
int PrevBlockID = (*PI)->getBlockID();
CFGBlockInfo *PrevBlockInfo = &BlockInfo[PrevBlockID];
// Ignore edges from blocks that can't return.
if ((*PI)->hasNoReturnElement() || !PrevBlockInfo->Reachable)
continue;
// Okay, we can reach this block from the entry.
CurrBlockInfo->Reachable = true;
// If the previous block ended in a 'continue' or 'break' statement, then
// a difference in locksets is probably due to a bug in that block, rather
// than in some other predecessor. In that case, keep the other
// predecessor's lockset.
if (const Stmt *Terminator = (*PI)->getTerminator()) {
if (isa<ContinueStmt>(Terminator) || isa<BreakStmt>(Terminator)) {
SpecialBlocks.push_back(*PI);
continue;
}
}
FactSet PrevLockset;
getEdgeLockset(PrevLockset, PrevBlockInfo->ExitSet, *PI, CurrBlock);
if (!LocksetInitialized) {
CurrBlockInfo->EntrySet = PrevLockset;
LocksetInitialized = true;
} else {
intersectAndWarn(CurrBlockInfo->EntrySet, PrevLockset,
CurrBlockInfo->EntryLoc,
LEK_LockedSomePredecessors);
}
}
// Skip rest of block if it's not reachable.
if (!CurrBlockInfo->Reachable)
continue;
// Process continue and break blocks. Assume that the lockset for the
// resulting block is unaffected by any discrepancies in them.
for (unsigned SpecialI = 0, SpecialN = SpecialBlocks.size();
SpecialI < SpecialN; ++SpecialI) {
CFGBlock *PrevBlock = SpecialBlocks[SpecialI];
int PrevBlockID = PrevBlock->getBlockID();
CFGBlockInfo *PrevBlockInfo = &BlockInfo[PrevBlockID];
if (!LocksetInitialized) {
CurrBlockInfo->EntrySet = PrevBlockInfo->ExitSet;
LocksetInitialized = true;
} else {
// Determine whether this edge is a loop terminator for diagnostic
// purposes. FIXME: A 'break' statement might be a loop terminator, but
// it might also be part of a switch. Also, a subsequent destructor
// might add to the lockset, in which case the real issue might be a
// double lock on the other path.
const Stmt *Terminator = PrevBlock->getTerminator();
bool IsLoop = Terminator && isa<ContinueStmt>(Terminator);
FactSet PrevLockset;
getEdgeLockset(PrevLockset, PrevBlockInfo->ExitSet,
PrevBlock, CurrBlock);
// Do not update EntrySet.
intersectAndWarn(CurrBlockInfo->EntrySet, PrevLockset,
PrevBlockInfo->ExitLoc,
IsLoop ? LEK_LockedSomeLoopIterations
: LEK_LockedSomePredecessors,
false);
}
}
BuildLockset LocksetBuilder(this, *CurrBlockInfo);
// Visit all the statements in the basic block.
for (CFGBlock::const_iterator BI = CurrBlock->begin(),
BE = CurrBlock->end(); BI != BE; ++BI) {
switch (BI->getKind()) {
case CFGElement::Statement: {
const CFGStmt *CS = cast<CFGStmt>(&*BI);
LocksetBuilder.Visit(const_cast<Stmt*>(CS->getStmt()));
break;
}
// Ignore BaseDtor, MemberDtor, and TemporaryDtor for now.
case CFGElement::AutomaticObjectDtor: {
const CFGAutomaticObjDtor *AD = cast<CFGAutomaticObjDtor>(&*BI);
CXXDestructorDecl *DD = const_cast<CXXDestructorDecl*>(
AD->getDestructorDecl(AC.getASTContext()));
if (!DD->hasAttrs())
break;
// Create a dummy expression,
VarDecl *VD = const_cast<VarDecl*>(AD->getVarDecl());
DeclRefExpr DRE(VD, false, VD->getType(), VK_LValue,
AD->getTriggerStmt()->getLocEnd());
LocksetBuilder.handleCall(&DRE, DD);
break;
}
default:
break;
}
}
CurrBlockInfo->ExitSet = LocksetBuilder.FSet;
// For every back edge from CurrBlock (the end of the loop) to another block
// (FirstLoopBlock) we need to check that the Lockset of Block is equal to
// the one held at the beginning of FirstLoopBlock. We can look up the
// Lockset held at the beginning of FirstLoopBlock in the EntryLockSets map.
for (CFGBlock::const_succ_iterator SI = CurrBlock->succ_begin(),
SE = CurrBlock->succ_end(); SI != SE; ++SI) {
// if CurrBlock -> *SI is *not* a back edge
if (*SI == 0 || !VisitedBlocks.alreadySet(*SI))
continue;
CFGBlock *FirstLoopBlock = *SI;
CFGBlockInfo *PreLoop = &BlockInfo[FirstLoopBlock->getBlockID()];
CFGBlockInfo *LoopEnd = &BlockInfo[CurrBlockID];
intersectAndWarn(LoopEnd->ExitSet, PreLoop->EntrySet,
PreLoop->EntryLoc,
LEK_LockedSomeLoopIterations,
false);
}
}
CFGBlockInfo *Initial = &BlockInfo[CFGraph->getEntry().getBlockID()];
CFGBlockInfo *Final = &BlockInfo[CFGraph->getExit().getBlockID()];
// Skip the final check if the exit block is unreachable.
if (!Final->Reachable)
return;
// FIXME: Should we call this function for all blocks which exit the function?
intersectAndWarn(Initial->EntrySet, Final->ExitSet,
Final->ExitLoc,
LEK_LockedAtEndOfFunction,
LEK_NotLockedAtEndOfFunction,
false);
}
} // end anonymous namespace
namespace clang {
namespace thread_safety {
/// \brief Check a function's CFG for thread-safety violations.
///
/// We traverse the blocks in the CFG, compute the set of mutexes that are held
/// at the end of each block, and issue warnings for thread safety violations.
/// Each block in the CFG is traversed exactly once.
void runThreadSafetyAnalysis(AnalysisDeclContext &AC,
ThreadSafetyHandler &Handler) {
ThreadSafetyAnalyzer Analyzer(Handler);
Analyzer.runAnalysis(AC);
}
/// \brief Helper function that returns a LockKind required for the given level
/// of access.
LockKind getLockKindFromAccessKind(AccessKind AK) {
switch (AK) {
case AK_Read :
return LK_Shared;
case AK_Written :
return LK_Exclusive;
}
llvm_unreachable("Unknown AccessKind");
}
}} // end namespace clang::thread_safety