virtual void warnLoopStateMismatch(SourceLocation Loc,
StringRef VariableName) {}
+ /// \brief Warn about parameter typestate mismatches upon return.
+ ///
+ /// \param Loc -- The SourceLocation of the return statement.
+ ///
+ /// \param ExpectedState -- The state the return value was expected to be
+ /// in.
+ ///
+ /// \param ObservedState -- The state the return value was observed to be
+ /// in.
+ virtual void warnParamReturnTypestateMismatch(SourceLocation Loc,
+ StringRef VariableName,
+ 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.
StringRef TypeName) {}
/// \brief Warn about return typestate mismatches.
+ ///
/// \param Loc -- The SourceLocation of the return statement.
+ ///
+ /// \param ExpectedState -- The state the return value was expected to be
+ /// in.
+ ///
+ /// \param ObservedState -- The state the return value was observed to be
+ /// in.
virtual void warnReturnTypestateMismatch(SourceLocation Loc,
StringRef ExpectedState,
StringRef ObservedState) {}
ConsumedStateMap(const ConsumedStateMap &Other)
: Reachable(Other.Reachable), From(Other.From), Map(Other.Map) {}
+ /// \brief Warn if any of the parameters being tracked are not in the state
+ /// they were declared to be in upon return from a function.
+ void checkParamsForReturnTypestate(SourceLocation BlameLoc,
+ ConsumedWarningsHandlerBase &WarningsHandler) const;
+
/// \brief Get the consumed state of a given variable.
ConsumedState getState(const VarDecl *Var) const;
def ReturnTypestate : InheritableAttr {
let Spellings = [GNU<"return_typestate">];
- let Subjects = [Function];
+ let Subjects = [Function, ParmVar];
let Args = [EnumArgument<"State", "ConsumedState",
["unknown", "consumed", "unconsumed"],
["Unknown", "Consumed", "Unconsumed"]>];
def warn_loop_state_mismatch : Warning<
"state of variable '%0' must match at the entry and exit of loop">,
InGroup<Consumed>, DefaultIgnore;
+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_impcast_vector_scalar : Warning<
"implicit conversion turns vector to scalar: %0 to %1">,
Loc = getFirstStmtLoc(*Block->succ_begin());
if (Loc.isValid())
return Loc;
-
+
// If we have one predecessor, return the last statement in that block
if (Block->pred_size() == 1 && *Block->pred_begin())
return getLastStmtLoc(*Block->pred_begin());
unsigned Offset = Call->getNumArgs() - FunDecl->getNumParams();
for (unsigned Index = Offset; Index < Call->getNumArgs(); ++Index) {
- QualType ParamType = FunDecl->getParamDecl(Index - Offset)->getType();
+ const ParmVarDecl *Param = FunDecl->getParamDecl(Index - Offset);
+ QualType ParamType = Param->getType();
InfoEntry Entry = PropagationMap.find(Call->getArg(Index));
StateMap->setState(PInfo.getVar(), consumed::CS_Consumed);
+ } else if (Param->hasAttr<ReturnTypestateAttr>()) {
+ StateMap->setState(PInfo.getVar(),
+ mapReturnTypestateAttrState(Param->getAttr<ReturnTypestateAttr>()));
+
} else if (!(ParamType.isConstQualified() ||
((ParamType->isReferenceType() ||
ParamType->isPointerType()) &&
}
void ConsumedStmtVisitor::VisitReturnStmt(const ReturnStmt *Ret) {
- if (ConsumedState ExpectedState = Analyzer.getExpectedReturnState()) {
+ ConsumedState ExpectedState = Analyzer.getExpectedReturnState();
+
+ if (ExpectedState != CS_None) {
InfoEntry Entry = PropagationMap.find(Ret->getRetValue());
if (Entry != PropagationMap.end()) {
stateToString(RetState));
}
}
+
+ StateMap->checkParamsForReturnTypestate(Ret->getLocStart(),
+ Analyzer.WarningsHandler);
}
void ConsumedStmtVisitor::VisitUnaryOperator(const UnaryOperator *UOp) {
return false;
}
+void ConsumedStateMap::checkParamsForReturnTypestate(SourceLocation BlameLoc,
+ ConsumedWarningsHandlerBase &WarningsHandler) const {
+
+ ConsumedState ExpectedState;
+
+ for (MapType::const_iterator DMI = Map.begin(), DME = Map.end(); DMI != DME;
+ ++DMI) {
+
+ if (isa<ParmVarDecl>(DMI->first)) {
+ const ParmVarDecl *Param = cast<ParmVarDecl>(DMI->first);
+
+ if (!Param->hasAttr<ReturnTypestateAttr>()) continue;
+
+ ExpectedState =
+ mapReturnTypestateAttrState(Param->getAttr<ReturnTypestateAttr>());
+
+ if (DMI->second != ExpectedState) {
+ WarningsHandler.warnParamReturnTypestateMismatch(BlameLoc,
+ Param->getNameAsString(), stateToString(ExpectedState),
+ stateToString(DMI->second));
+ }
+ }
+ }
+}
+
ConsumedState ConsumedStateMap::getState(const VarDecl *Var) const {
MapType::const_iterator Entry = Map.find(Var);
CurrStates = NULL;
}
}
+
+ if (CurrBlock == &AC.getCFG()->getExit() &&
+ D->getCallResultType()->isVoidType())
+ CurrStates->checkParamsForReturnTypestate(D->getLocation(),
+ WarningsHandler);
} // End of block iterator.
// Delete the last existing state map.
Warnings.push_back(DelayedDiag(Warning, OptionalNotes()));
}
+ void warnParamReturnTypestateMismatch(SourceLocation Loc,
+ StringRef VariableName,
+ StringRef ExpectedState,
+ StringRef ObservedState) {
+
+ PartialDiagnosticAt Warning(Loc, S.PDiag(
+ diag::warn_param_return_typestate_mismatch) << VariableName <<
+ ExpectedState << ObservedState);
+
+ Warnings.push_back(DelayedDiag(Warning, OptionalNotes()));
+ }
+
void warnReturnTypestateForUnconsumableType(SourceLocation Loc,
StringRef TypeName) {
PartialDiagnosticAt Warning(Loc, S.PDiag(
static void handleReturnTypestateAttr(Sema &S, Decl *D,
const AttributeList &Attr) {
+ if (!checkAttributeNumArgs(S, Attr, 1)) return;
+
+ if (!(isa<FunctionDecl>(D) || isa<ParmVarDecl>(D))) {
+ S.Diag(Attr.getLoc(), diag::warn_attribute_wrong_decl_type) <<
+ Attr.getName() << ExpectedFunctionMethodOrParameter;
+ return;
+ }
+
ReturnTypestateAttr::ConsumedState ReturnState;
if (Attr.isArgIdent(0)) {
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)) {
+ //if (const ParmVarDecl *Param = dyn_cast<ParmVarDecl>(D)) {
+ // ReturnType = Param->getType();
+ //
+ //} else if (const CXXConstructorDecl *Constructor =
+ // dyn_cast<CXXConstructorDecl>(D)) {
// ReturnType = Constructor->getThisType(S.getASTContext())->getPointeeType();
//
//} else {
*param; // expected-warning {{invocation of method 'operator*' on object 'param' while it is in the 'consumed' state}}
}
+void testParamReturnTypestateCallee(bool cond, ConsumableClass<int> &Param RETURN_TYPESTATE(unconsumed)) { // expected-warning {{parameter 'Param' not in expected state when the function returns: expected 'unconsumed', observed 'consumed'}}
+
+ if (cond) {
+ Param.consume();
+ return; // expected-warning {{parameter 'Param' not in expected state when the function returns: expected 'unconsumed', observed 'consumed'}}
+ }
+
+ Param.consume();
+}
+
+void testParamReturnTypestateCaller() {
+ ConsumableClass<int> var;
+
+ testParamReturnTypestateCallee(true, var);
+
+ *var;
+}
+
void testCallingConventions() {
ConsumableClass<int> var(42);
AttrTester1 returnTypestateTester0() RETURN_TYPESTATE(not_a_state); // expected-warning {{'return_typestate' attribute argument not supported: 'not_a_state'}}
AttrTester1 returnTypestateTester1() RETURN_TYPESTATE(42); // expected-error {{'return_typestate' attribute requires an identifier}}
+void returnTypestateTester2(AttrTester1 &Param RETURN_TYPESTATE(unconsumed));
+
class AttrTester2 {
void callableWhen() CALLABLE_WHEN("unconsumed"); // expected-warning {{consumed analysis attribute is attached to member of class 'AttrTester2' which isn't marked as consumable}}
void consumes() SET_TYPESTATE(consumed); // expected-warning {{consumed analysis attribute is attached to member of class 'AttrTester2' which isn't marked as consumable}}