/// \brief Emit the warnings and notes left by the analysis.
virtual void emitDiagnostics() {}
-
+
+ // FIXME: This can be removed when the attr propagation fix for templated
+ // classes lands.
+ /// \brief Warn about return typestates set for unconsumable types.
+ ///
+ /// \param Loc -- The location of the attributes.
+ ///
+ /// \param TypeName -- The name of the unconsumable type.
+ virtual void warnReturnTypestateForUnconsumableType(SourceLocation Loc,
+ StringRef TypeName) {}
+
+ /// \brief Warn about return typestate mismatches.
+ /// \param Loc -- The SourceLocation of the return statement.
+ virtual void warnReturnTypestateMismatch(SourceLocation Loc,
+ StringRef ExpectedState,
+ StringRef ObservedState) {}
+
/// \brief Warn about unnecessary-test errors.
/// \param VariableName -- The name of the variable that holds the unique
/// value.
///
+ /// \param VariableState -- The known state of the value.
+ ///
/// \param Loc -- The SourceLocation of the unnecessary test.
virtual void warnUnnecessaryTest(StringRef VariableName,
StringRef VariableState,
ConsumedBlockInfo BlockInfo;
ConsumedStateMap *CurrStates;
+ ConsumedState ExpectedReturnState;
+
bool hasConsumableAttributes(const CXXRecordDecl *RD);
bool splitState(const CFGBlock *CurrBlock,
const ConsumedStmtVisitor &Visitor);
ConsumedAnalyzer(ConsumedWarningsHandlerBase &WarningsHandler)
: WarningsHandler(WarningsHandler) {}
+ ConsumedState getExpectedReturnState() const { return ExpectedReturnState; }
+
/// \brief Check a function's CFG for consumed violations.
///
/// We traverse the blocks in the CFG, keeping track of the state of each
let Subjects = [CXXMethod];
}
+def ReturnTypestate : InheritableAttr {
+ let Spellings = [GNU<"return_typestate">];
+ let Subjects = [Function];
+ let Args = [EnumArgument<"State", "ConsumedState",
+ ["unknown", "consumed", "unconsumed"],
+ ["Unknown", "Consumed", "Unconsumed"]>];
+}
+
// Type safety attributes for `void *' pointers and type tags.
def ArgumentWithTypeTag : InheritableAttr {
"invocation of method '%0' on a temporary object while it is in the "
"'consumed' state">, InGroup<Consumed>, DefaultIgnore;
def warn_attr_on_unconsumable_class : Warning<
- "consumed analysis attribute is attached to class '%0' which isn't marked "
- "as consumable">, InGroup<Consumed>, DefaultIgnore;
+ "consumed analysis attribute is attached to member of class '%0' which isn't "
+ "marked as consumable">, InGroup<Consumed>, DefaultIgnore;
+def warn_return_typestate_for_unconsumable_type : Warning<
+ "return state set for an unconsumable type '%0'">, InGroup<Consumed>,
+ DefaultIgnore;
+def warn_unknown_consumed_state : Warning<
+ "unknown consumed analysis state '%0'">, InGroup<Consumed>, DefaultIgnore;
+def warn_return_typestate_mismatch : Warning<
+ "return value not in expected state; expected '%0', observed '%1'">,
+ InGroup<Consumed>, DefaultIgnore;
// ConsumedStrict warnings
def warn_use_in_unknown_state : Warning<
#include "llvm/Support/Compiler.h"
#include "llvm/Support/raw_ostream.h"
+// TODO: Add notes about the actual and expected state for
// TODO: Correctly identify unreachable blocks when chaining boolean operators.
// TODO: Warn about unreachable code.
// TODO: Switch to using a bitmap to track unreachable blocks.
return FunDecl->hasAttr<TestsUnconsumedAttr>();
}
+static ConsumedState mapReturnTypestateAttrState(
+ const ReturnTypestateAttr *RTSAttr) {
+
+ switch (RTSAttr->getState()) {
+ case ReturnTypestateAttr::Unknown:
+ return CS_Unknown;
+ case ReturnTypestateAttr::Unconsumed:
+ return CS_Unconsumed;
+ case ReturnTypestateAttr::Consumed:
+ return CS_Consumed;
+ }
+}
+
static StringRef stateToString(ConsumedState State) {
switch (State) {
case consumed::CS_None:
void forwardInfo(const Stmt *From, const Stmt *To);
void handleTestingFunctionCall(const CallExpr *Call, const VarDecl *Var);
bool isLikeMoveAssignment(const CXXMethodDecl *MethodDecl);
+ void propagateReturnType(const Stmt *Call, const FunctionDecl *Fun,
+ QualType ReturnType);
public:
void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *Temp);
void VisitMemberExpr(const MemberExpr *MExpr);
void VisitParmVarDecl(const ParmVarDecl *Param);
+ void VisitReturnStmt(const ReturnStmt *Ret);
void VisitUnaryOperator(const UnaryOperator *UOp);
void VisitVarDecl(const VarDecl *Var);
MethodDecl->getParamDecl(0)->getType()->isRValueReferenceType());
}
+void ConsumedStmtVisitor::propagateReturnType(const Stmt *Call,
+ const FunctionDecl *Fun,
+ QualType ReturnType) {
+ if (isConsumableType(ReturnType)) {
+
+ ConsumedState ReturnState;
+
+ if (Fun->hasAttr<ReturnTypestateAttr>())
+ ReturnState = mapReturnTypestateAttrState(
+ Fun->getAttr<ReturnTypestateAttr>());
+ else
+ ReturnState = CS_Unknown;
+
+ PropagationMap.insert(PairType(Call,
+ PropagationInfo(ReturnState)));
+ }
+}
+
void ConsumedStmtVisitor::Visit(const Stmt *StmtNode) {
ConstStmtVisitor<ConsumedStmtVisitor>::Visit(StmtNode);
StateMap->setState(PInfo.getVar(), consumed::CS_Unknown);
}
}
+
+ propagateReturnType(Call, FunDecl, FunDecl->getCallResultType());
}
}
QualType ThisType = Constructor->getThisType(CurrContext)->getPointeeType();
if (isConsumableType(ThisType)) {
- if (Constructor->hasAttr<ConsumesAttr>() ||
- Constructor->isDefaultConstructor()) {
+ if (Constructor->isDefaultConstructor()) {
PropagationMap.insert(PairType(Call,
PropagationInfo(consumed::CS_Consumed)));
PropagationMap.insert(PairType(Call, Entry->second));
} else {
- PropagationMap.insert(PairType(Call,
- PropagationInfo(consumed::CS_Unconsumed)));
+ propagateReturnType(Call, Constructor, ThisType);
}
}
}
StateMap->setState(Param, consumed::CS_Unknown);
}
+void ConsumedStmtVisitor::VisitReturnStmt(const ReturnStmt *Ret) {
+ if (ConsumedState ExpectedState = Analyzer.getExpectedReturnState()) {
+ InfoEntry Entry = PropagationMap.find(Ret->getRetValue());
+
+ if (Entry != PropagationMap.end()) {
+ assert(Entry->second.isState() || Entry->second.isVar());
+
+ ConsumedState RetState = Entry->second.isState() ?
+ Entry->second.getState() : StateMap->getState(Entry->second.getVar());
+
+ if (RetState != ExpectedState)
+ Analyzer.WarningsHandler.warnReturnTypestateMismatch(
+ Ret->getReturnLoc(), stateToString(ExpectedState),
+ stateToString(RetState));
+ }
+ }
+}
+
void ConsumedStmtVisitor::VisitUnaryOperator(const UnaryOperator *UOp) {
InfoEntry Entry = PropagationMap.find(UOp->getSubExpr()->IgnoreParens());
if (Entry == PropagationMap.end()) return;
if (!D) return;
+ // FIXME: This should be removed when template instantiation propagates
+ // attributes at template specialization definition, not declaration.
+ // When it is removed the test needs to be enabled in SemaDeclAttr.cpp.
+ QualType ReturnType;
+ if (const CXXConstructorDecl *Constructor = dyn_cast<CXXConstructorDecl>(D)) {
+ ASTContext &CurrContext = AC.getASTContext();
+ ReturnType = Constructor->getThisType(CurrContext)->getPointeeType();
+
+ } else {
+ ReturnType = D->getCallResultType();
+ }
+
+ // Determine the expected return value.
+ if (D->hasAttr<ReturnTypestateAttr>()) {
+
+ ReturnTypestateAttr *RTSAttr = D->getAttr<ReturnTypestateAttr>();
+
+ const CXXRecordDecl *RD = ReturnType->getAsCXXRecordDecl();
+ if (!RD || !RD->hasAttr<ConsumableAttr>()) {
+ // FIXME: This branch can be removed with the code above.
+ WarningsHandler.warnReturnTypestateForUnconsumableType(
+ RTSAttr->getLocation(), ReturnType.getAsString());
+ ExpectedReturnState = CS_None;
+
+ } else {
+ switch (RTSAttr->getState()) {
+ case ReturnTypestateAttr::Unknown:
+ ExpectedReturnState = CS_Unknown;
+ break;
+
+ case ReturnTypestateAttr::Unconsumed:
+ ExpectedReturnState = CS_Unconsumed;
+ break;
+
+ case ReturnTypestateAttr::Consumed:
+ ExpectedReturnState = CS_Consumed;
+ break;
+ }
+ }
+
+ } else if (isConsumableType(ReturnType)) {
+ ExpectedReturnState = CS_Unknown;
+
+ } else {
+ ExpectedReturnState = CS_None;
+ }
+
BlockInfo = ConsumedBlockInfo(AC.getCFG());
PostOrderCFGView *SortedGraph = AC.getAnalysis<PostOrderCFGView>();
}
}
- /// Warn about unnecessary-test errors.
- /// \param VariableName -- The name of the variable that holds the unique
- /// value.
- ///
- /// \param Loc -- The SourceLocation of the unnecessary test.
+ void warnReturnTypestateForUnconsumableType(SourceLocation Loc,
+ StringRef TypeName) {
+ PartialDiagnosticAt Warning(Loc, S.PDiag(
+ diag::warn_return_typestate_for_unconsumable_type) << TypeName);
+
+ Warnings.push_back(DelayedDiag(Warning, OptionalNotes()));
+ }
+
+ void warnReturnTypestateMismatch(SourceLocation Loc, StringRef ExpectedState,
+ StringRef ObservedState) {
+
+ PartialDiagnosticAt Warning(Loc, S.PDiag(
+ diag::warn_return_typestate_mismatch) << ExpectedState << ObservedState);
+
+ Warnings.push_back(DelayedDiag(Warning, OptionalNotes()));
+ }
+
void warnUnnecessaryTest(StringRef VariableName, StringRef VariableState,
SourceLocation Loc) {
Warnings.push_back(DelayedDiag(Warning, OptionalNotes()));
}
- /// Warn about use-while-consumed errors.
- /// \param MethodName -- The name of the method that was incorrectly
- /// invoked.
- ///
- /// \param Loc -- The SourceLocation of the method invocation.
void warnUseOfTempWhileConsumed(StringRef MethodName, SourceLocation Loc) {
PartialDiagnosticAt Warning(Loc, S.PDiag(
Warnings.push_back(DelayedDiag(Warning, OptionalNotes()));
}
- /// Warn about use-in-unknown-state errors.
- /// \param MethodName -- The name of the method that was incorrectly
- /// invoked.
- ///
- /// \param Loc -- The SourceLocation of the method invocation.
void warnUseOfTempInUnknownState(StringRef MethodName, SourceLocation Loc) {
PartialDiagnosticAt Warning(Loc, S.PDiag(
Warnings.push_back(DelayedDiag(Warning, OptionalNotes()));
}
- /// Warn about use-while-consumed errors.
- /// \param MethodName -- The name of the method that was incorrectly
- /// invoked.
- ///
- /// \param VariableName -- The name of the variable that holds the unique
- /// value.
- ///
- /// \param Loc -- The SourceLocation of the method invocation.
void warnUseWhileConsumed(StringRef MethodName, StringRef VariableName,
SourceLocation Loc) {
Warnings.push_back(DelayedDiag(Warning, OptionalNotes()));
}
- /// Warn about use-in-unknown-state errors.
- /// \param MethodName -- The name of the method that was incorrectly
- /// invoked.
- ///
- /// \param VariableName -- The name of the variable that holds the unique
- /// value.
- ///
- /// \param Loc -- The SourceLocation of the method invocation.
void warnUseInUnknownState(StringRef MethodName, StringRef VariableName,
SourceLocation Loc) {
Attr.getAttributeSpellingListIndex()));
}
+static void handleReturnTypestateAttr(Sema &S, Decl *D,
+ const AttributeList &Attr) {
+ if (!checkAttributeNumArgs(S, Attr, 1)) return;
+
+ ReturnTypestateAttr::ConsumedState ReturnState;
+
+ if (Attr.isArgIdent(0)) {
+ StringRef Param = Attr.getArgAsIdent(0)->Ident->getName();
+
+ if (Param == "unknown") {
+ ReturnState = ReturnTypestateAttr::Unknown;
+ } else if (Param == "consumed") {
+ ReturnState = ReturnTypestateAttr::Consumed;
+ } else if (Param == "unconsumed") {
+ ReturnState = ReturnTypestateAttr::Unconsumed;
+ } else {
+ S.Diag(Attr.getLoc(), diag::warn_unknown_consumed_state) << Param;
+ return;
+ }
+
+ } else {
+ S.Diag(Attr.getLoc(), diag::err_attribute_argument_type) <<
+ Attr.getName() << AANT_ArgumentIdentifier;
+ return;
+ }
+
+ if (!isa<FunctionDecl>(D)) {
+ S.Diag(Attr.getLoc(), diag::warn_attribute_wrong_decl_type) <<
+ Attr.getName() << ExpectedFunction;
+ return;
+ }
+
+ // FIXME: This check is currently being done in the analysis. It can be
+ // enabled here only after the parser propagates attributes at
+ // template specialization definition, not declaration.
+ //QualType ReturnType;
+ //
+ //if (const CXXConstructorDecl *Constructor = dyn_cast<CXXConstructorDecl>(D)) {
+ // ReturnType = Constructor->getThisType(S.getASTContext())->getPointeeType();
+ //
+ //} else {
+ //
+ // ReturnType = cast<FunctionDecl>(D)->getCallResultType();
+ //}
+ //
+ //const CXXRecordDecl *RD = ReturnType->getAsCXXRecordDecl();
+ //
+ //if (!RD || !RD->hasAttr<ConsumableAttr>()) {
+ // S.Diag(Attr.getLoc(), diag::warn_return_state_for_unconsumable_type) <<
+ // ReturnType.getAsString();
+ // return;
+ //}
+
+ D->addAttr(::new (S.Context)
+ ReturnTypestateAttr(Attr.getRange(), S.Context, ReturnState,
+ Attr.getAttributeSpellingListIndex()));
+}
+
static void handleExtVectorTypeAttr(Sema &S, Scope *scope, Decl *D,
const AttributeList &Attr) {
TypedefNameDecl *TD = dyn_cast<TypedefNameDecl>(D);
case AttributeList::AT_TestsUnconsumed:
handleTestsUnconsumedAttr(S, D, Attr);
break;
+ case AttributeList::AT_ReturnTypestate:
+ handleReturnTypestateAttr(S, D, Attr);
+ break;
// Type safety attributes.
case AttributeList::AT_ArgumentWithTypeTag:
#define CALLABLE_WHEN_UNCONSUMED __attribute__ ((callable_when_unconsumed))
#define CONSUMABLE __attribute__ ((consumable))
#define CONSUMES __attribute__ ((consumes))
+#define RETURN_TYPESTATE(State) __attribute__ ((return_typestate(State)))
#define TESTS_UNCONSUMED __attribute__ ((tests_unconsumed))
#define TEST_VAR(Var) Var.isValid()
public:
ConsumableClass();
- ConsumableClass(T val);
- ConsumableClass(ConsumableClass<T> &other);
- ConsumableClass(ConsumableClass<T> &&other);
+ ConsumableClass(nullptr_t p) RETURN_TYPESTATE(consumed);
+ ConsumableClass(T val) RETURN_TYPESTATE(unconsumed);
+ ConsumableClass(ConsumableClass<T> &other) RETURN_TYPESTATE(unconsumed);
+ ConsumableClass(ConsumableClass<T> &&other) RETURN_TYPESTATE(unconsumed);
ConsumableClass<T>& operator=(ConsumableClass<T> &other);
ConsumableClass<T>& operator=(ConsumableClass<T> &&other);
#define CALLABLE_WHEN_UNCONSUMED __attribute__ ((callable_when_unconsumed))
#define CONSUMABLE __attribute__ ((consumable))
#define CONSUMES __attribute__ ((consumes))
+#define RETURN_TYPESTATE(State) __attribute__ ((return_typestate(State)))
#define TESTS_UNCONSUMED __attribute__ ((tests_unconsumed))
typedef decltype(nullptr) nullptr_t;
public:
ConsumableClass();
- ConsumableClass(nullptr_t p) CONSUMES;
- ConsumableClass(T val);
- ConsumableClass(ConsumableClass<T> &other);
- ConsumableClass(ConsumableClass<T> &&other);
+ ConsumableClass(nullptr_t p) RETURN_TYPESTATE(consumed);
+ ConsumableClass(T val) RETURN_TYPESTATE(unconsumed);
+ ConsumableClass(ConsumableClass<T> &other) RETURN_TYPESTATE(unconsumed);
+ ConsumableClass(ConsumableClass<T> &&other) RETURN_TYPESTATE(unconsumed);
ConsumableClass<T>& operator=(ConsumableClass<T> &other);
ConsumableClass<T>& operator=(ConsumableClass<T> &&other);
void baf3(ConsumableClass<int> &&var);
+ConsumableClass<int> returnsUnconsumed() RETURN_TYPESTATE(unconsumed);
+ConsumableClass<int> returnsUnconsumed() {
+ return ConsumableClass<int>(); // expected-warning {{return value not in expected state; expected 'unconsumed', observed 'consumed'}}
+}
+
+ConsumableClass<int> returnsConsumed() RETURN_TYPESTATE(consumed);
+ConsumableClass<int> returnsConsumed() {
+ return ConsumableClass<int>();
+}
+
void testInitialization() {
ConsumableClass<int> var0;
ConsumableClass<int> var1 = ConsumableClass<int>();
*var; // expected-warning {{invocation of method 'operator*' on object 'var' while it is in the 'consumed' state}}
}
+void testReturnStates() {
+ ConsumableClass<int> var;
+
+ var = returnsUnconsumed();
+ *var;
+
+ var = returnsConsumed();
+ *var; // expected-warning {{invocation of method 'operator*' on object 'var' while it is in the 'consumed' state}}
+}
+
void testMoveAsignmentish() {
ConsumableClass<int> var0;
ConsumableClass<long> var1(42);
// RUN: %clang_cc1 -fsyntax-only -verify -Wconsumed -std=c++11 %s
-#define CONSUMABLE __attribute__ ((consumable))
-#define CONSUMES __attribute__ ((consumes))
-#define TESTS_UNCONSUMED __attribute__ ((tests_unconsumed))
-#define CALLABLE_WHEN_UNCONSUMED __attribute__ ((callable_when_unconsumed))
+#define CALLABLE_WHEN_UNCONSUMED __attribute__ ((callable_when_unconsumed))
+#define CONSUMABLE __attribute__ ((consumable))
+#define CONSUMES __attribute__ ((consumes))
+#define RETURN_TYPESTATE(State) __attribute__ ((return_typestate(State)))
+#define TESTS_UNCONSUMED __attribute__ ((tests_unconsumed))
+
+// FIXME: This warning is not generated if it appears bellow the AttrTester0
+// class declaration. Why?
+int returnTypestateForUnconsumable() RETURN_TYPESTATE(consumed); // expected-warning {{return state set for an unconsumable type 'int'}}
+int returnTypestateForUnconsumable() {
+ return 0;
+}
class AttrTester0 {
- void Consumes() __attribute__ ((consumes(42))); // expected-error {{attribute takes no arguments}}
- bool TestsUnconsumed() __attribute__ ((tests_unconsumed(42))); // expected-error {{attribute takes no arguments}}
- void CallableWhenUnconsumed()
- __attribute__ ((callable_when_unconsumed(42))); // expected-error {{attribute takes no arguments}}
+ void consumes() __attribute__ ((consumes(42))); // expected-error {{attribute takes no arguments}}
+ bool testsUnconsumed() __attribute__ ((tests_unconsumed(42))); // expected-error {{attribute takes no arguments}}
+ void callableWhenUnconsumed() __attribute__ ((callable_when_unconsumed(42))); // expected-error {{attribute takes no arguments}}
};
int var0 CONSUMES; // expected-warning {{'consumes' attribute only applies to methods}}
int var1 TESTS_UNCONSUMED; // expected-warning {{'tests_unconsumed' attribute only applies to methods}}
int var2 CALLABLE_WHEN_UNCONSUMED; // expected-warning {{'callable_when_unconsumed' attribute only applies to methods}}
int var3 CONSUMABLE; // expected-warning {{'consumable' attribute only applies to classes}}
+int var4 RETURN_TYPESTATE(consumed); // expected-warning {{'return_typestate' attribute only applies to functions}}
void function0() CONSUMES; // expected-warning {{'consumes' attribute only applies to methods}}
void function1() TESTS_UNCONSUMED; // expected-warning {{'tests_unconsumed' attribute only applies to methods}}
bool testsUnconsumed() TESTS_UNCONSUMED;
};
+AttrTester1 returnTypestateTester0() RETURN_TYPESTATE(not_a_state); // expected-warning {{unknown consumed analysis state 'not_a_state'}}
+AttrTester1 returnTypestateTester1() RETURN_TYPESTATE(42); // expected-error {{'return_typestate' attribute requires an identifier}}
+
class AttrTester2 {
- void callableWhenUnconsumed() CALLABLE_WHEN_UNCONSUMED; // expected-warning {{consumed analysis attribute is attached to class 'AttrTester2' which isn't marked as consumable}}
- void consumes() CONSUMES; // expected-warning {{consumed analysis attribute is attached to class 'AttrTester2' which isn't marked as consumable}}
- bool testsUnconsumed() TESTS_UNCONSUMED; // expected-warning {{consumed analysis attribute is attached to class 'AttrTester2' which isn't marked as consumable}}
+ void callableWhenUnconsumed() CALLABLE_WHEN_UNCONSUMED; // expected-warning {{consumed analysis attribute is attached to member of class 'AttrTester2' which isn't marked as consumable}}
+ void consumes() CONSUMES; // expected-warning {{consumed analysis attribute is attached to member of class 'AttrTester2' which isn't marked as consumable}}
+ bool testsUnconsumed() TESTS_UNCONSUMED; // expected-warning {{consumed analysis attribute is attached to member of class 'AttrTester2' which isn't marked as consumable}}
};