forked from OSchip/llvm-project
Update to consumed analysis.
Patch by chris.wailes@gmail.com. The following functionality was added: * The same functionality is now supported for both CXXOperatorCallExprs and CXXMemberCallExprs. * Factored out some code in StmtVisitor. * Removed variables from the state map when their destructors are encountered. * Started adding documentation for the consumed analysis attributes. llvm-svn: 189059
This commit is contained in:
parent
b1e159257c
commit
c2ecf0d815
|
@ -2022,6 +2022,33 @@ to specify that the function must be called while holding the listed shared
|
|||
locks. Arguments must be lockable type, and there must be at least one
|
||||
argument.
|
||||
|
||||
Consumed Annotation Checking
|
||||
============================
|
||||
|
||||
Clang supports additional attributes for checking basic resource management
|
||||
properties, specifically for unique objects that have a single owning reference.
|
||||
The following attributes are currently supported, although **the implementation
|
||||
for these annotations is currently in development and are subject to change.**
|
||||
|
||||
``consumes``
|
||||
------------
|
||||
|
||||
Use ``__attribute__((consumes))`` on a method that transitions an object into
|
||||
the consumed state.
|
||||
|
||||
``callable_when_unconsumed``
|
||||
----------------------------
|
||||
|
||||
Use ``__attribute__((callable_when_unconsumed))`` to indicate that a method may
|
||||
only be called when the object is not in the consumed state.
|
||||
|
||||
``tests_unconsumed``
|
||||
--------------------
|
||||
|
||||
Use `__attribute__((tests_unconsumed))`` to indicate that a method returns true
|
||||
if the object is in the unconsumed state.
|
||||
|
||||
|
||||
Type Safety Checking
|
||||
====================
|
||||
|
||||
|
|
|
@ -118,6 +118,9 @@ namespace consumed {
|
|||
|
||||
/// \brief Set the consumed state of a given variable.
|
||||
void setState(const VarDecl *Var, ConsumedState State);
|
||||
|
||||
/// \brief Remove the variable from our state map.
|
||||
void remove(const VarDecl *Var);
|
||||
};
|
||||
|
||||
class ConsumedBlockInfo {
|
||||
|
@ -186,10 +189,6 @@ namespace consumed {
|
|||
/// exactly once.
|
||||
void run(AnalysisDeclContext &AC);
|
||||
};
|
||||
|
||||
/// \brief Check to see if a function tests an object's validity.
|
||||
bool isTestingFunction(const CXXMethodDecl *MethodDecl);
|
||||
|
||||
}} // end namespace clang::consumed
|
||||
|
||||
#endif
|
||||
|
|
|
@ -932,7 +932,7 @@ def TestsUnconsumed : InheritableAttr {
|
|||
|
||||
def Consumes : InheritableAttr {
|
||||
let Spellings = [GNU<"consumes">];
|
||||
let Subjects = [CXXMethod];
|
||||
let Subjects = [CXXMethod, CXXConstructor];
|
||||
}
|
||||
|
||||
def TestsConsumed : InheritableAttr {
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
|
||||
// TODO: Add support for methods with CallableWhenUnconsumed.
|
||||
// TODO: Mark variables as Unknown going into while- or for-loops only if they
|
||||
// are referenced inside that block. (Deferred)
|
||||
// TODO: Add a method(s) to identify which method calls perform what state
|
||||
|
@ -48,6 +47,10 @@ using namespace consumed;
|
|||
// Key method definition
|
||||
ConsumedWarningsHandlerBase::~ConsumedWarningsHandlerBase() {}
|
||||
|
||||
static bool isTestingFunction(const FunctionDecl *FunDecl) {
|
||||
return FunDecl->hasAttr<TestsUnconsumedAttr>();
|
||||
}
|
||||
|
||||
static StringRef stateToString(ConsumedState State) {
|
||||
switch (State) {
|
||||
case consumed::CS_None:
|
||||
|
@ -91,9 +94,9 @@ class ConsumedStmtVisitor : public ConstStmtVisitor<ConsumedStmtVisitor> {
|
|||
StateOrVar.Var = Var;
|
||||
}
|
||||
|
||||
ConsumedState getState() { return StateOrVar.State; };
|
||||
ConsumedState getState() const { return StateOrVar.State; }
|
||||
|
||||
const VarDecl * getVar() { return IsVar ? StateOrVar.Var : NULL; };
|
||||
const VarDecl * getVar() const { return IsVar ? StateOrVar.Var : NULL; }
|
||||
};
|
||||
|
||||
typedef llvm::DenseMap<const Stmt *, PropagationInfo> MapType;
|
||||
|
@ -105,6 +108,9 @@ class ConsumedStmtVisitor : public ConstStmtVisitor<ConsumedStmtVisitor> {
|
|||
ConsumedStateMap *StateMap;
|
||||
MapType PropagationMap;
|
||||
|
||||
void checkCallability(const PropagationInfo &PState,
|
||||
const FunctionDecl *FunDecl,
|
||||
const CallExpr *Call);
|
||||
void forwardInfo(const Stmt *From, const Stmt *To);
|
||||
bool isLikeMoveAssignment(const CXXMethodDecl *MethodDecl);
|
||||
|
||||
|
@ -134,6 +140,54 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
// TODO: When we support CallableWhenConsumed this will have to check for
|
||||
// the different attributes and change the behavior bellow. (Deferred)
|
||||
void ConsumedStmtVisitor::checkCallability(const PropagationInfo &PState,
|
||||
const FunctionDecl *FunDecl,
|
||||
const CallExpr *Call) {
|
||||
|
||||
if (!FunDecl->hasAttr<CallableWhenUnconsumedAttr>()) return;
|
||||
|
||||
if (PState.IsVar) {
|
||||
const VarDecl *Var = PState.getVar();
|
||||
|
||||
switch (StateMap->getState(Var)) {
|
||||
case CS_Consumed:
|
||||
Analyzer.WarningsHandler.warnUseWhileConsumed(
|
||||
FunDecl->getNameAsString(), Var->getNameAsString(),
|
||||
Call->getExprLoc());
|
||||
break;
|
||||
|
||||
case CS_Unknown:
|
||||
Analyzer.WarningsHandler.warnUseInUnknownState(
|
||||
FunDecl->getNameAsString(), Var->getNameAsString(),
|
||||
Call->getExprLoc());
|
||||
break;
|
||||
|
||||
case CS_None:
|
||||
case CS_Unconsumed:
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
switch (PState.getState()) {
|
||||
case CS_Consumed:
|
||||
Analyzer.WarningsHandler.warnUseOfTempWhileConsumed(
|
||||
FunDecl->getNameAsString(), Call->getExprLoc());
|
||||
break;
|
||||
|
||||
case CS_Unknown:
|
||||
Analyzer.WarningsHandler.warnUseOfTempInUnknownState(
|
||||
FunDecl->getNameAsString(), Call->getExprLoc());
|
||||
break;
|
||||
|
||||
case CS_None:
|
||||
case CS_Unconsumed:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConsumedStmtVisitor::forwardInfo(const Stmt *From, const Stmt *To) {
|
||||
InfoEntry Entry = PropagationMap.find(From);
|
||||
|
||||
|
@ -278,14 +332,16 @@ void ConsumedStmtVisitor::VisitCXXMemberCallExpr(
|
|||
|
||||
if (Entry != PropagationMap.end()) {
|
||||
PropagationInfo PState = Entry->second;
|
||||
if (!PState.IsVar) return;
|
||||
const CXXMethodDecl *MethodDecl = Call->getMethodDecl();
|
||||
|
||||
const CXXMethodDecl *Method = Call->getMethodDecl();
|
||||
checkCallability(PState, MethodDecl, Call);
|
||||
|
||||
if (Method->hasAttr<ConsumesAttr>())
|
||||
StateMap->setState(PState.getVar(), consumed::CS_Consumed);
|
||||
else if (!Method->isConst())
|
||||
StateMap->setState(PState.getVar(), consumed::CS_Unknown);
|
||||
if (PState.IsVar) {
|
||||
if (MethodDecl->hasAttr<ConsumesAttr>())
|
||||
StateMap->setState(PState.getVar(), consumed::CS_Consumed);
|
||||
else if (!MethodDecl->isConst())
|
||||
StateMap->setState(PState.getVar(), consumed::CS_Unknown);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -374,57 +430,21 @@ void ConsumedStmtVisitor::VisitCXXOperatorCallExpr(
|
|||
InfoEntry Entry = PropagationMap.find(Call->getArg(0));
|
||||
|
||||
if (Entry != PropagationMap.end()) {
|
||||
|
||||
PropagationInfo PState = Entry->second;
|
||||
|
||||
// TODO: When we support CallableWhenConsumed this will have to check for
|
||||
// the different attributes and change the behavior bellow.
|
||||
// (Deferred)
|
||||
if (FunDecl->hasAttr<CallableWhenUnconsumedAttr>()) {
|
||||
if (PState.IsVar) {
|
||||
const VarDecl *Var = PState.getVar();
|
||||
|
||||
switch (StateMap->getState(Var)) {
|
||||
case CS_Consumed:
|
||||
Analyzer.WarningsHandler.warnUseWhileConsumed(
|
||||
FunDecl->getNameAsString(), Var->getNameAsString(),
|
||||
Call->getExprLoc());
|
||||
break;
|
||||
|
||||
case CS_Unknown:
|
||||
Analyzer.WarningsHandler.warnUseInUnknownState(
|
||||
FunDecl->getNameAsString(), Var->getNameAsString(),
|
||||
Call->getExprLoc());
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
switch (PState.getState()) {
|
||||
case CS_Consumed:
|
||||
Analyzer.WarningsHandler.warnUseOfTempWhileConsumed(
|
||||
FunDecl->getNameAsString(), Call->getExprLoc());
|
||||
break;
|
||||
|
||||
case CS_Unknown:
|
||||
Analyzer.WarningsHandler.warnUseOfTempInUnknownState(
|
||||
FunDecl->getNameAsString(), Call->getExprLoc());
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
checkCallability(PState, FunDecl, Call);
|
||||
|
||||
// Handle non-constant member operators.
|
||||
if (const CXXMethodDecl *MethodDecl =
|
||||
dyn_cast_or_null<CXXMethodDecl>(FunDecl)) {
|
||||
|
||||
if (!MethodDecl->isConst() && PState.IsVar)
|
||||
StateMap->setState(PState.getVar(), consumed::CS_Unknown);
|
||||
if (PState.IsVar) {
|
||||
if (FunDecl->hasAttr<ConsumesAttr>()) {
|
||||
// Handle consuming operators.
|
||||
StateMap->setState(PState.getVar(), consumed::CS_Consumed);
|
||||
} else if (const CXXMethodDecl *MethodDecl =
|
||||
dyn_cast_or_null<CXXMethodDecl>(FunDecl)) {
|
||||
|
||||
// Handle non-constant member operators.
|
||||
if (!MethodDecl->isConst())
|
||||
StateMap->setState(PState.getVar(), consumed::CS_Unknown);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -505,10 +525,10 @@ public:
|
|||
};
|
||||
|
||||
bool TestedVarsVisitor::VisitCallExpr(CallExpr *Call) {
|
||||
if (const CXXMethodDecl *Method =
|
||||
dyn_cast_or_null<CXXMethodDecl>(Call->getDirectCallee())) {
|
||||
if (const FunctionDecl *FunDecl =
|
||||
dyn_cast_or_null<FunctionDecl>(Call->getDirectCallee())) {
|
||||
|
||||
if (isTestingFunction(Method)) {
|
||||
if (isTestingFunction(FunDecl)) {
|
||||
CurrTestLoc = Call->getExprLoc();
|
||||
IsUsefulConditional = true;
|
||||
return true;
|
||||
|
@ -521,16 +541,11 @@ bool TestedVarsVisitor::VisitCallExpr(CallExpr *Call) {
|
|||
}
|
||||
|
||||
bool TestedVarsVisitor::VisitDeclRefExpr(DeclRefExpr *DeclRef) {
|
||||
if (const VarDecl *Var = dyn_cast_or_null<VarDecl>(DeclRef->getDecl())) {
|
||||
if (StateMap->getState(Var) != consumed::CS_None) {
|
||||
if (const VarDecl *Var = dyn_cast_or_null<VarDecl>(DeclRef->getDecl()))
|
||||
if (StateMap->getState(Var) != consumed::CS_None)
|
||||
Test = VarTestResult(Var, CurrTestLoc, !Invert);
|
||||
}
|
||||
|
||||
} else {
|
||||
IsUsefulConditional = false;
|
||||
}
|
||||
|
||||
return IsUsefulConditional;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TestedVarsVisitor::VisitUnaryOperator(UnaryOperator *UnaryOp) {
|
||||
|
@ -637,6 +652,9 @@ void ConsumedStateMap::setState(const VarDecl *Var, ConsumedState State) {
|
|||
Map[Var] = State;
|
||||
}
|
||||
|
||||
void ConsumedStateMap::remove(const VarDecl *Var) {
|
||||
Map.erase(Var);
|
||||
}
|
||||
|
||||
bool ConsumedAnalyzer::isConsumableType(QualType Type) {
|
||||
const CXXRecordDecl *RD =
|
||||
|
@ -741,13 +759,17 @@ void ConsumedAnalyzer::run(AnalysisDeclContext &AC) {
|
|||
for (CFGBlock::const_iterator BI = CurrBlock->begin(),
|
||||
BE = CurrBlock->end(); BI != BE; ++BI) {
|
||||
|
||||
if (BI->getKind() == CFGElement::Statement)
|
||||
switch (BI->getKind()) {
|
||||
case CFGElement::Statement:
|
||||
Visitor.Visit(BI->castAs<CFGStmt>().getStmt());
|
||||
break;
|
||||
case CFGElement::AutomaticObjectDtor:
|
||||
CurrStates->remove(BI->castAs<CFGAutomaticObjDtor>().getVarDecl());
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove any variables that have reached the end of their
|
||||
// lifetimes from the state map. (Deferred)
|
||||
|
||||
if (const IfStmt *Terminator =
|
||||
dyn_cast_or_null<IfStmt>(CurrBlock->getTerminator().getStmt())) {
|
||||
|
||||
|
@ -785,9 +807,4 @@ void ConsumedAnalyzer::run(AnalysisDeclContext &AC) {
|
|||
|
||||
WarningsHandler.emitDiagnostics();
|
||||
}
|
||||
|
||||
bool isTestingFunction(const CXXMethodDecl *Method) {
|
||||
return Method->hasAttr<TestsUnconsumedAttr>();
|
||||
}
|
||||
|
||||
}} // end namespace clang::consumed
|
||||
|
|
|
@ -1551,10 +1551,9 @@ clang::sema::AnalysisBasedWarnings::AnalysisBasedWarnings(Sema &s)
|
|||
DefaultPolicy.enableThreadSafetyAnalysis = (unsigned)
|
||||
(D.getDiagnosticLevel(diag::warn_double_lock, SourceLocation()) !=
|
||||
DiagnosticsEngine::Ignored);
|
||||
DefaultPolicy.enableConsumedAnalysis =
|
||||
(unsigned)(D.getDiagnosticLevel(diag::warn_use_while_consumed,
|
||||
SourceLocation()) !=
|
||||
DiagnosticsEngine::Ignored);
|
||||
DefaultPolicy.enableConsumedAnalysis = (unsigned)
|
||||
(D.getDiagnosticLevel(diag::warn_use_while_consumed, SourceLocation()) !=
|
||||
DiagnosticsEngine::Ignored);
|
||||
}
|
||||
|
||||
static void flushDiagnostics(Sema &S, sema::FunctionScopeInfo *fscope) {
|
||||
|
|
|
@ -27,10 +27,13 @@ class Bar {
|
|||
template <typename U>
|
||||
Bar<T>& operator=(Bar<U> &&other);
|
||||
|
||||
void operator()(int a) CONSUMES;
|
||||
void operator*(void) const CALLABLE_WHEN_UNCONSUMED;
|
||||
void unconsumedCall(void) const CALLABLE_WHEN_UNCONSUMED;
|
||||
|
||||
bool isValid(void) const TESTS_UNCONSUMED;
|
||||
operator bool() const TESTS_UNCONSUMED;
|
||||
bool operator!=(nullptr_t) const TESTS_UNCONSUMED;
|
||||
|
||||
void constCall(void) const;
|
||||
void nonconstCall(void);
|
||||
|
@ -62,6 +65,10 @@ void testInitialization(void) {
|
|||
}
|
||||
}
|
||||
|
||||
void testTempValue(void) {
|
||||
*Bar<int>(); // expected-warning {{invocation of method 'operator*' on a temporary object while it is in the 'consumed' state}}
|
||||
}
|
||||
|
||||
void testSimpleRValueRefs(void) {
|
||||
Bar<int> var0;
|
||||
Bar<int> var1(42);
|
||||
|
@ -98,6 +105,13 @@ void testIfStmt(void) {
|
|||
} else {
|
||||
*var; // expected-warning {{invocation of method 'operator*' on object 'var' while it is in the 'consumed' state}}
|
||||
}
|
||||
|
||||
if (var != nullptr) {
|
||||
// Empty
|
||||
|
||||
} else {
|
||||
*var; // expected-warning {{invocation of method 'operator*' on object 'var' while it is in the 'consumed' state}}
|
||||
}
|
||||
}
|
||||
|
||||
void testCallingConventions(void) {
|
||||
|
@ -164,6 +178,15 @@ void testConsumes1(void) {
|
|||
*var; // expected-warning {{invocation of method 'operator*' on object 'var' while it is in the 'consumed' state}}
|
||||
}
|
||||
|
||||
void testConsumes2(void) {
|
||||
Bar<int> var(42);
|
||||
|
||||
var.unconsumedCall();
|
||||
var(6);
|
||||
|
||||
var.unconsumedCall(); // expected-warning {{invocation of method 'unconsumedCall' on object 'var' while it is in the 'consumed' state}}
|
||||
}
|
||||
|
||||
void testSimpleForLoop(void) {
|
||||
Bar<int> var;
|
||||
|
||||
|
|
Loading…
Reference in New Issue