]> granicus.if.org Git - clang/commitdiff
Consumed analysis: Add param_typestate attribute, which specifies that
authorDeLesley Hutchins <delesley@google.com>
Thu, 17 Oct 2013 23:23:53 +0000 (23:23 +0000)
committerDeLesley Hutchins <delesley@google.com>
Thu, 17 Oct 2013 23:23:53 +0000 (23:23 +0000)
function parameters must be in a particular state.  Patch by
chris.wailes@gmail.com.  Reviewed by delesley@google.com.

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@192934 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.cpp

index 69bd022ba239d51915af37dec9c0f73d4f3f6e50..e1968ed3da9463ebaeb56a92209fd58724d4fd56 100644 (file)
@@ -73,6 +73,11 @@ namespace consumed {
                                                   StringRef ExpectedState,
                                                   StringRef ObservedState) {};
     
+    // FIXME: Add documentation.
+    virtual void warnParamTypestateMismatch(SourceLocation LOC,
+                                            StringRef ExpectedState,
+                                            StringRef ObservedState) {}
+    
     // FIXME: This can be removed when the attr propagation fix for templated
     //        classes lands.
     /// \brief Warn about return typestates set for unconsumable types.
index 0521830efcee671609657bcf6905bab3a04b62fc..c9db43bbb4dcfb441f5bb1e8e067188ca4fe0442 100644 (file)
@@ -967,6 +967,14 @@ def CallableWhen : InheritableAttr {
                                    ["Unknown", "Consumed", "Unconsumed"]>];
 }
 
+def ParamTypestate : InheritableAttr {
+  let Spellings = [GNU<"param_typestate">];
+  let Subjects = [ParmVar];
+  let Args = [EnumArgument<"ParamState", "ConsumedState",
+                           ["unknown", "consumed", "unconsumed"],
+                           ["Unknown", "Consumed", "Unconsumed"]>];
+}
+
 def ReturnTypestate : InheritableAttr {
   let Spellings = [GNU<"return_typestate">];
   let Subjects = [Function, ParmVar];
index 399e9d7a2b8e93323fe4e686911425dd717216af..9d6ea40c69338090fbf2123971c41a85df377a6b 100644 (file)
@@ -2225,6 +2225,9 @@ def warn_loop_state_mismatch : Warning<
 def warn_param_return_typestate_mismatch : Warning<
   "parameter '%0' not in expected state when the function returns: expected "
   "'%1', observed '%2'">, InGroup<Consumed>, DefaultIgnore;
+def warn_param_typestate_mismatch : Warning<
+  "argument not in expected state; expected '%0', observed '%1'">,
+  InGroup<Consumed>, DefaultIgnore;
 
 def warn_impcast_vector_scalar : Warning<
   "implicit conversion turns vector to scalar: %0 to %1">,
index 021c26dff2f014241b1c1a4254ea4a69728399ef..a7d747f705b2da5174770dbd004acd16906390a5 100644 (file)
@@ -180,13 +180,14 @@ static ConsumedState mapConsumableAttrState(const QualType QT) {
   llvm_unreachable("invalid enum");
 }
 
-static ConsumedState mapSetTypestateAttrState(const SetTypestateAttr *STAttr) {
-  switch (STAttr->getNewState()) {
-  case SetTypestateAttr::Unknown:
+static ConsumedState
+mapParamTypestateAttrState(const ParamTypestateAttr *PTAttr) {
+  switch (PTAttr->getParamState()) {
+  case ParamTypestateAttr::Unknown:
     return CS_Unknown;
-  case SetTypestateAttr::Unconsumed:
+  case ParamTypestateAttr::Unconsumed:
     return CS_Unconsumed;
-  case SetTypestateAttr::Consumed:
+  case ParamTypestateAttr::Consumed:
     return CS_Consumed;
   }
   llvm_unreachable("invalid_enum");
@@ -205,6 +206,18 @@ mapReturnTypestateAttrState(const ReturnTypestateAttr *RTSAttr) {
   llvm_unreachable("invalid enum");
 }
 
+static ConsumedState mapSetTypestateAttrState(const SetTypestateAttr *STAttr) {
+  switch (STAttr->getNewState()) {
+  case SetTypestateAttr::Unknown:
+    return CS_Unknown;
+  case SetTypestateAttr::Unconsumed:
+    return CS_Unconsumed;
+  case SetTypestateAttr::Consumed:
+    return CS_Consumed;
+  }
+  llvm_unreachable("invalid_enum");
+}
+
 static StringRef stateToString(ConsumedState State) {
   switch (State) {
   case consumed::CS_None:
@@ -577,12 +590,33 @@ void ConsumedStmtVisitor::VisitCallExpr(const CallExpr *Call) {
       
       InfoEntry Entry = PropagationMap.find(Call->getArg(Index));
       
-      if (Entry == PropagationMap.end() || !Entry->second.isVar()) {
+      if (Entry == PropagationMap.end() ||
+          !(Entry->second.isState() || Entry->second.isVar()))
         continue;
-      }
       
       PropagationInfo PInfo = Entry->second;
       
+      // Check that the parameter is in the correct state.
+      
+      if (Param->hasAttr<ParamTypestateAttr>()) {
+        ConsumedState ParamState =
+          PInfo.isState() ? PInfo.getState() :
+                            StateMap->getState(PInfo.getVar());
+        
+        ConsumedState ExpectedState =
+          mapParamTypestateAttrState(Param->getAttr<ParamTypestateAttr>());
+        
+        if (ParamState != ExpectedState)
+          Analyzer.WarningsHandler.warnParamTypestateMismatch(
+            Call->getArg(Index - Offset)->getExprLoc(),
+            stateToString(ExpectedState), stateToString(ParamState));
+      }
+      
+      if (!Entry->second.isVar())
+        continue;
+      
+      // Adjust state on the caller side.
+      
       if (ParamType->isRValueReferenceType() ||
           (ParamType->isLValueReferenceType() &&
            !cast<LValueReferenceType>(*ParamType).isSpelledAsLValue())) {
@@ -812,15 +846,22 @@ void ConsumedStmtVisitor::VisitMemberExpr(const MemberExpr *MExpr) {
 void ConsumedStmtVisitor::VisitParmVarDecl(const ParmVarDecl *Param) {
   QualType ParamType = Param->getType();
   ConsumedState ParamState = consumed::CS_None;
-
-  if (!(ParamType->isPointerType() || ParamType->isReferenceType()) &&
-      isConsumableType(ParamType))
+  
+  if (Param->hasAttr<ParamTypestateAttr>()) {
+    ParamState =
+      mapParamTypestateAttrState(Param->getAttr<ParamTypestateAttr>());
+    
+  } else if (!(ParamType->isPointerType() || ParamType->isReferenceType()) &&
+             isConsumableType(ParamType)) {
+    
     ParamState = mapConsumableAttrState(ParamType);
-  else if (ParamType->isReferenceType() &&
-           isConsumableType(ParamType->getPointeeType()))
+    
+  } else if (ParamType->isReferenceType() &&
+             isConsumableType(ParamType->getPointeeType())) {
     ParamState = consumed::CS_Unknown;
-
-  if (ParamState)
+  }
+  
+  if (ParamState != CS_None)
     StateMap->setState(Param, ParamState);
 }
 
index 2d65980ec07ac53254650bf1c7888780a1870a81..93e3ecfb29472113fe3e6726f21a0ab33b33397d 100644 (file)
@@ -1496,6 +1496,15 @@ public:
     Warnings.push_back(DelayedDiag(Warning, OptionalNotes()));
   }
   
+  void warnParamTypestateMismatch(SourceLocation Loc, StringRef ExpectedState,
+                                  StringRef ObservedState) {
+    
+    PartialDiagnosticAt Warning(Loc, S.PDiag(
+      diag::warn_param_typestate_mismatch) << ExpectedState << ObservedState);
+    
+    Warnings.push_back(DelayedDiag(Warning, OptionalNotes()));
+  }
+  
   void warnReturnTypestateForUnconsumableType(SourceLocation Loc,
                                               StringRef TypeName) {
     PartialDiagnosticAt Warning(Loc, S.PDiag(
index c3e6bc4cc347fc578aa55cbd1840e5cc8e1550cc..3eabf87624c03dec9442af1ad2e2f3709b8822fa 100644 (file)
@@ -1051,7 +1051,7 @@ static void handleCallableWhenAttr(Sema &S, Decl *D,
       return;
 
     if (!CallableWhenAttr::ConvertStrToConsumedState(StateString,
-                                                      CallableState)) {
+                                                     CallableState)) {
       S.Diag(Loc, diag::warn_attribute_type_not_supported)
         << Attr.getName() << StateString;
       return;
@@ -1066,6 +1066,52 @@ static void handleCallableWhenAttr(Sema &S, Decl *D,
 }
 
 
+static void handleParamTypestateAttr(Sema &S, Decl *D,
+                                    const AttributeList &Attr) {
+  if (!checkAttributeNumArgs(S, Attr, 1)) return;
+  
+  if (!isa<ParmVarDecl>(D)) {
+    S.Diag(Attr.getLoc(), diag::warn_attribute_wrong_decl_type) <<
+      Attr.getName() << ExpectedParameter;
+    return;
+  }
+  
+  ParamTypestateAttr::ConsumedState ParamState;
+  
+  if (Attr.isArgIdent(0)) {
+    IdentifierLoc *Ident = Attr.getArgAsIdent(0);
+    StringRef StateString = Ident->Ident->getName();
+
+    if (!ParamTypestateAttr::ConvertStrToConsumedState(StateString,
+                                                       ParamState)) {
+      S.Diag(Ident->Loc, diag::warn_attribute_type_not_supported)
+        << Attr.getName() << StateString;
+      return;
+    }
+  } else {
+    S.Diag(Attr.getLoc(), diag::err_attribute_argument_type) <<
+      Attr.getName() << AANT_ArgumentIdentifier;
+    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 = cast<ParmVarDecl>(D)->getType();
+  //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)
+             ParamTypestateAttr(Attr.getRange(), S.Context, ParamState,
+                                Attr.getAttributeSpellingListIndex()));
+}
+
+
 static void handleReturnTypestateAttr(Sema &S, Decl *D,
                                       const AttributeList &Attr) {
   if (!checkAttributeNumArgs(S, Attr, 1)) return;
@@ -4818,6 +4864,9 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D,
   case AttributeList::AT_CallableWhen:
     handleCallableWhenAttr(S, D, Attr);
     break;
+  case AttributeList::AT_ParamTypestate:
+    handleParamTypestateAttr(S, D, Attr);
+    break;
   case AttributeList::AT_ReturnTypestate:
     handleReturnTypestateAttr(S, D, Attr);
     break;
index dd1bb2312d8ebecab255397a4bd8b22c2f3443cd..2e45216cfe9b8978f3a4bd8cff4daeb699e17c86 100644 (file)
@@ -4,8 +4,9 @@
 
 #define CALLABLE_WHEN(...)      __attribute__ ((callable_when(__VA_ARGS__)))
 #define CONSUMABLE(state)       __attribute__ ((consumable(state)))
-#define SET_TYPESTATE(state)    __attribute__ ((set_typestate(state)))
+#define PARAM_TYPESTATE(state)  __attribute__ ((param_typestate(state)))
 #define RETURN_TYPESTATE(state) __attribute__ ((return_typestate(state)))
+#define SET_TYPESTATE(state)    __attribute__ ((set_typestate(state)))
 #define TESTS_TYPESTATE(state)  __attribute__ ((tests_typestate(state)))
 
 typedef decltype(nullptr) nullptr_t;
@@ -406,6 +407,19 @@ void testParamReturnTypestateCaller() {
   *var;
 }
 
+void testParamTypestateCallee(ConsumableClass<int>  Param0 PARAM_TYPESTATE(consumed),
+                              ConsumableClass<int> &Param1 PARAM_TYPESTATE(consumed)) {
+  
+  *Param0; // expected-warning {{invalid invocation of method 'operator*' on object 'Param0' while it is in the 'consumed' state}}
+  *Param1; // expected-warning {{invalid invocation of method 'operator*' on object 'Param1' while it is in the 'consumed' state}}
+}
+
+void testParamTypestateCaller() {
+  ConsumableClass<int> Var0, Var1(42);
+  
+  testParamTypestateCallee(Var0, Var1); // expected-warning {{argument not in expected state; expected 'consumed', observed 'unconsumed'}}
+}
+
 void testCallingConventions() {
   ConsumableClass<int> var(42);