summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDeLesley Hutchins <delesley@google.com>2013-10-17 23:23:53 +0000
committerDeLesley Hutchins <delesley@google.com>2013-10-17 23:23:53 +0000
commitd4f0e1991f42c69111213699fb2d09dedee1cd36 (patch)
treefee34accea7d2277e2a7eb798f7160575d82c88c
parentd33884f1e2e3189ee2db75cc72d90ea854f6bc68 (diff)
downloadclang-d4f0e1991f42c69111213699fb2d09dedee1cd36.tar.gz
Consumed analysis: Add param_typestate attribute, which specifies that
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
-rw-r--r--include/clang/Analysis/Analyses/Consumed.h5
-rw-r--r--include/clang/Basic/Attr.td8
-rw-r--r--include/clang/Basic/DiagnosticSemaKinds.td3
-rw-r--r--lib/Analysis/Consumed.cpp69
-rw-r--r--lib/Sema/AnalysisBasedWarnings.cpp9
-rw-r--r--lib/Sema/SemaDeclAttr.cpp51
-rw-r--r--test/SemaCXX/warn-consumed-analysis.cpp16
7 files changed, 145 insertions, 16 deletions
diff --git a/include/clang/Analysis/Analyses/Consumed.h b/include/clang/Analysis/Analyses/Consumed.h
index 69bd022ba2..e1968ed3da 100644
--- a/include/clang/Analysis/Analyses/Consumed.h
+++ b/include/clang/Analysis/Analyses/Consumed.h
@@ -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.
diff --git a/include/clang/Basic/Attr.td b/include/clang/Basic/Attr.td
index 0521830efc..c9db43bbb4 100644
--- a/include/clang/Basic/Attr.td
+++ b/include/clang/Basic/Attr.td
@@ -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];
diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td
index 399e9d7a2b..9d6ea40c69 100644
--- a/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/include/clang/Basic/DiagnosticSemaKinds.td
@@ -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">,
diff --git a/lib/Analysis/Consumed.cpp b/lib/Analysis/Consumed.cpp
index 021c26dff2..a7d747f705 100644
--- a/lib/Analysis/Consumed.cpp
+++ b/lib/Analysis/Consumed.cpp
@@ -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);
}
diff --git a/lib/Sema/AnalysisBasedWarnings.cpp b/lib/Sema/AnalysisBasedWarnings.cpp
index 2d65980ec0..93e3ecfb29 100644
--- a/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/lib/Sema/AnalysisBasedWarnings.cpp
@@ -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(
diff --git a/lib/Sema/SemaDeclAttr.cpp b/lib/Sema/SemaDeclAttr.cpp
index c3e6bc4cc3..3eabf87624 100644
--- a/lib/Sema/SemaDeclAttr.cpp
+++ b/lib/Sema/SemaDeclAttr.cpp
@@ -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;
diff --git a/test/SemaCXX/warn-consumed-analysis.cpp b/test/SemaCXX/warn-consumed-analysis.cpp
index dd1bb2312d..2e45216cfe 100644
--- a/test/SemaCXX/warn-consumed-analysis.cpp
+++ b/test/SemaCXX/warn-consumed-analysis.cpp
@@ -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);