diff options
author | DeLesley Hutchins <delesley@google.com> | 2013-10-17 23:23:53 +0000 |
---|---|---|
committer | DeLesley Hutchins <delesley@google.com> | 2013-10-17 23:23:53 +0000 |
commit | d4f0e1991f42c69111213699fb2d09dedee1cd36 (patch) | |
tree | fee34accea7d2277e2a7eb798f7160575d82c88c | |
parent | d33884f1e2e3189ee2db75cc72d90ea854f6bc68 (diff) | |
download | clang-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.h | 5 | ||||
-rw-r--r-- | include/clang/Basic/Attr.td | 8 | ||||
-rw-r--r-- | include/clang/Basic/DiagnosticSemaKinds.td | 3 | ||||
-rw-r--r-- | lib/Analysis/Consumed.cpp | 69 | ||||
-rw-r--r-- | lib/Sema/AnalysisBasedWarnings.cpp | 9 | ||||
-rw-r--r-- | lib/Sema/SemaDeclAttr.cpp | 51 | ||||
-rw-r--r-- | test/SemaCXX/warn-consumed-analysis.cpp | 16 |
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); |