]> granicus.if.org Git - clang/commitdiff
Consumed analysis: add return_typestate attribute.
authorDeLesley Hutchins <delesley@google.com>
Tue, 3 Sep 2013 20:11:38 +0000 (20:11 +0000)
committerDeLesley Hutchins <delesley@google.com>
Tue, 3 Sep 2013 20:11:38 +0000 (20:11 +0000)
Patch by chris.wailes@gmail.com

Functions can now declare what state the consumable type the are returning will
be in. This is then used on the caller side and checked on the callee side.
Constructors now use this attribute instead of the 'consumes' attribute.

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@189843 91177308-0d34-0410-b5e6-96231b3b80d8

include/clang/Analysis/Analyses/Consumed.h
include/clang/Basic/Attr.td
include/clang/Basic/DiagnosticSemaKinds.td
lib/Analysis/Consumed.cpp
lib/Sema/AnalysisBasedWarnings.cpp
lib/Sema/SemaDeclAttr.cpp
test/SemaCXX/warn-consumed-analysis-strict.cpp
test/SemaCXX/warn-consumed-analysis.cpp
test/SemaCXX/warn-consumed-parsing.cpp

index dfc4f5d2fe51103ed280988dca0f8bc1a7d27112..082baad1767143c26672482ac45d4d616fdcebc8 100644 (file)
@@ -39,11 +39,29 @@ namespace consumed {
 
     /// \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,
@@ -170,6 +188,8 @@ namespace consumed {
     ConsumedBlockInfo BlockInfo;
     ConsumedStateMap *CurrStates;
     
+    ConsumedState ExpectedReturnState;
+    
     bool hasConsumableAttributes(const CXXRecordDecl *RD);
     bool splitState(const CFGBlock *CurrBlock,
                     const ConsumedStmtVisitor &Visitor);
@@ -181,6 +201,8 @@ namespace consumed {
     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
index e244d4b9c0311b641b24432f78f22e0a0a12a676..9d5f414394db862d84b39634b10984d030bdc9f6 100644 (file)
@@ -953,6 +953,14 @@ def Consumes : InheritableAttr {
   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 {
index b7f8f0036956c9cf4df1cb7e298d57fb64bbf438..2e0a01a90df5a6126020b138d55d7c76e982581b 100644 (file)
@@ -2192,8 +2192,16 @@ def warn_use_of_temp_while_consumed : Warning<
   "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<
index 59ebbb2fc05046757eca0e5bf44fc0d2deff73b2..6ffdb23f2e9b21d78829b857e8b956746927b49b 100644 (file)
@@ -31,6 +31,7 @@
 #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.
@@ -88,6 +89,19 @@ static bool isTestingFunction(const FunctionDecl *FunDecl) {
   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:
@@ -256,6 +270,8 @@ class ConsumedStmtVisitor : public ConstStmtVisitor<ConsumedStmtVisitor> {
   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:
 
@@ -272,6 +288,7 @@ 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);
 
@@ -373,6 +390,24 @@ bool ConsumedStmtVisitor::isLikeMoveAssignment(
           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);
@@ -469,6 +504,8 @@ void ConsumedStmtVisitor::VisitCallExpr(const CallExpr *Call) {
         StateMap->setState(PInfo.getVar(), consumed::CS_Unknown);
       }
     }
+    
+    propagateReturnType(Call, FunDecl, FunDecl->getCallResultType());
   }
 }
 
@@ -483,8 +520,7 @@ void ConsumedStmtVisitor::VisitCXXConstructExpr(const CXXConstructExpr *Call) {
   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)));
@@ -513,8 +549,7 @@ void ConsumedStmtVisitor::VisitCXXConstructExpr(const CXXConstructExpr *Call) {
         PropagationMap.insert(PairType(Call, Entry->second));
       
     } else {
-      PropagationMap.insert(PairType(Call,
-        PropagationInfo(consumed::CS_Unconsumed)));
+      propagateReturnType(Call, Constructor, ThisType);
     }
   }
 }
@@ -677,6 +712,24 @@ void ConsumedStmtVisitor::VisitParmVarDecl(const ParmVarDecl *Param) {
     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;
@@ -997,6 +1050,53 @@ void ConsumedAnalyzer::run(AnalysisDeclContext &AC) {
   
   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>();
index 74de09a9566dca5d6e3980195e5e975e9eb827d9..d4997429edc0e381d009dbf8af6ea3fd2e8255a7 100644 (file)
@@ -1445,11 +1445,23 @@ public:
     }
   }
   
-  /// 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) {
 
@@ -1459,11 +1471,6 @@ public:
     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(
@@ -1472,11 +1479,6 @@ public:
     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(
@@ -1485,14 +1487,6 @@ public:
     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) {
   
@@ -1502,14 +1496,6 @@ public:
     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) {
 
index 00f8af9382bc0daff6a645ee8400f100c37d4aa0..cc4f107aeb5430d5ea4939375ba8ccd1e7b8d721 100644 (file)
@@ -1069,6 +1069,64 @@ static void handleTestsUnconsumedAttr(Sema &S, Decl *D,
                                  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);
@@ -5024,6 +5082,9 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *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:
index 499ba009bdd7a813edca3f48e1a2e9ae476fcee0..1bc604de479cf1ada1b503ef3be66664b05ff8ce 100644 (file)
@@ -3,6 +3,7 @@
 #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()
@@ -15,9 +16,10 @@ class CONSUMABLE ConsumableClass {
   
   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);
index d4387fc29deecb5e0bac499288859091827f91c7..53a29b128d96bde94e73122df68907bff2fce490 100644 (file)
@@ -3,6 +3,7 @@
 #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;
@@ -13,10 +14,10 @@ class CONSUMABLE ConsumableClass {
   
   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);
@@ -48,6 +49,16 @@ void baf2(const ConsumableClass<int> *var);
 
 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>();
@@ -253,6 +264,16 @@ void testCallingConventions() {
   *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);
index ae71e29dc7943b0747aac3d28588990bb27e15c0..674197f6e02bcb1409e382de6f32552a6b1b0e19 100644 (file)
@@ -1,21 +1,29 @@
 // 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}}
@@ -28,8 +36,11 @@ class CONSUMABLE AttrTester1 {
   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}}
 };