diff --git a/clang/lib/Analysis/Consumed.cpp b/clang/lib/Analysis/Consumed.cpp index 17bc29ba8834..cde753e8ec57 100644 --- a/clang/lib/Analysis/Consumed.cpp +++ b/clang/lib/Analysis/Consumed.cpp @@ -1,1415 +1,1415 @@ //===- Consumed.cpp -------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // A intra-procedural analysis for checking consumed properties. This is based, // in part, on research on linear types. // //===----------------------------------------------------------------------===// #include "clang/Analysis/Analyses/Consumed.h" #include "clang/AST/Attr.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/Stmt.h" #include "clang/AST/StmtVisitor.h" #include "clang/AST/Type.h" #include "clang/Analysis/Analyses/PostOrderCFGView.h" #include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Analysis/CFG.h" #include "clang/Basic/LLVM.h" #include "clang/Basic/OperatorKinds.h" #include "clang/Basic/SourceLocation.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" #include "llvm/Support/ErrorHandling.h" #include #include #include // TODO: Adjust states of args to constructors in the same way that arguments to // function calls are handled. // TODO: Use information from tests in for- and while-loop conditional. // TODO: Add notes about the actual and expected state for // TODO: Correctly identify unreachable blocks when chaining boolean operators. // TODO: Adjust the parser and AttributesList class to support lists of // identifiers. // TODO: Warn about unreachable code. // TODO: Switch to using a bitmap to track unreachable blocks. // TODO: Handle variable definitions, e.g. bool valid = x.isValid(); // if (valid) ...; (Deferred) // TODO: Take notes on state transitions to provide better warning messages. // (Deferred) // TODO: Test nested conditionals: A) Checking the same value multiple times, // and 2) Checking different values. (Deferred) using namespace clang; using namespace consumed; // Key method definition ConsumedWarningsHandlerBase::~ConsumedWarningsHandlerBase() = default; static SourceLocation getFirstStmtLoc(const CFGBlock *Block) { // Find the source location of the first statement in the block, if the block // is not empty. for (const auto &B : *Block) if (Optional CS = B.getAs()) return CS->getStmt()->getBeginLoc(); // Block is empty. // If we have one successor, return the first statement in that block if (Block->succ_size() == 1 && *Block->succ_begin()) return getFirstStmtLoc(*Block->succ_begin()); return {}; } static SourceLocation getLastStmtLoc(const CFGBlock *Block) { // Find the source location of the last statement in the block, if the block // is not empty. if (const Stmt *StmtNode = Block->getTerminatorStmt()) { return StmtNode->getBeginLoc(); } else { for (CFGBlock::const_reverse_iterator BI = Block->rbegin(), BE = Block->rend(); BI != BE; ++BI) { if (Optional CS = BI->getAs()) return CS->getStmt()->getBeginLoc(); } } // If we have one successor, return the first statement in that block SourceLocation Loc; if (Block->succ_size() == 1 && *Block->succ_begin()) 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()); return Loc; } static ConsumedState invertConsumedUnconsumed(ConsumedState State) { switch (State) { case CS_Unconsumed: return CS_Consumed; case CS_Consumed: return CS_Unconsumed; case CS_None: return CS_None; case CS_Unknown: return CS_Unknown; } llvm_unreachable("invalid enum"); } static bool isCallableInState(const CallableWhenAttr *CWAttr, ConsumedState State) { for (const auto &S : CWAttr->callableStates()) { ConsumedState MappedAttrState = CS_None; switch (S) { case CallableWhenAttr::Unknown: MappedAttrState = CS_Unknown; break; case CallableWhenAttr::Unconsumed: MappedAttrState = CS_Unconsumed; break; case CallableWhenAttr::Consumed: MappedAttrState = CS_Consumed; break; } if (MappedAttrState == State) return true; } return false; } static bool isConsumableType(const QualType &QT) { if (QT->isPointerType() || QT->isReferenceType()) return false; if (const CXXRecordDecl *RD = QT->getAsCXXRecordDecl()) return RD->hasAttr(); return false; } static bool isAutoCastType(const QualType &QT) { if (QT->isPointerType() || QT->isReferenceType()) return false; if (const CXXRecordDecl *RD = QT->getAsCXXRecordDecl()) return RD->hasAttr(); return false; } static bool isSetOnReadPtrType(const QualType &QT) { if (const CXXRecordDecl *RD = QT->getPointeeCXXRecordDecl()) return RD->hasAttr(); return false; } static bool isKnownState(ConsumedState State) { switch (State) { case CS_Unconsumed: case CS_Consumed: return true; case CS_None: case CS_Unknown: return false; } llvm_unreachable("invalid enum"); } static bool isRValueRef(QualType ParamType) { return ParamType->isRValueReferenceType(); } static bool isTestingFunction(const FunctionDecl *FunDecl) { return FunDecl->hasAttr(); } static bool isPointerOrRef(QualType ParamType) { return ParamType->isPointerType() || ParamType->isReferenceType(); } static ConsumedState mapConsumableAttrState(const QualType QT) { assert(isConsumableType(QT)); const ConsumableAttr *CAttr = QT->getAsCXXRecordDecl()->getAttr(); switch (CAttr->getDefaultState()) { case ConsumableAttr::Unknown: return CS_Unknown; case ConsumableAttr::Unconsumed: return CS_Unconsumed; case ConsumableAttr::Consumed: return CS_Consumed; } llvm_unreachable("invalid enum"); } static ConsumedState mapParamTypestateAttrState(const ParamTypestateAttr *PTAttr) { switch (PTAttr->getParamState()) { case ParamTypestateAttr::Unknown: return CS_Unknown; case ParamTypestateAttr::Unconsumed: return CS_Unconsumed; case ParamTypestateAttr::Consumed: return CS_Consumed; } llvm_unreachable("invalid_enum"); } 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; } 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: return "none"; case consumed::CS_Unknown: return "unknown"; case consumed::CS_Unconsumed: return "unconsumed"; case consumed::CS_Consumed: return "consumed"; } llvm_unreachable("invalid enum"); } static ConsumedState testsFor(const FunctionDecl *FunDecl) { assert(isTestingFunction(FunDecl)); switch (FunDecl->getAttr()->getTestState()) { case TestTypestateAttr::Unconsumed: return CS_Unconsumed; case TestTypestateAttr::Consumed: return CS_Consumed; } llvm_unreachable("invalid enum"); } namespace { struct VarTestResult { const VarDecl *Var; ConsumedState TestsFor; }; } // namespace namespace clang { namespace consumed { enum EffectiveOp { EO_And, EO_Or }; class PropagationInfo { enum { IT_None, IT_State, IT_VarTest, IT_BinTest, IT_Var, IT_Tmp } InfoType = IT_None; struct BinTestTy { const BinaryOperator *Source; EffectiveOp EOp; VarTestResult LTest; VarTestResult RTest; }; union { ConsumedState State; VarTestResult VarTest; const VarDecl *Var; const CXXBindTemporaryExpr *Tmp; BinTestTy BinTest; }; public: PropagationInfo() = default; PropagationInfo(const VarTestResult &VarTest) : InfoType(IT_VarTest), VarTest(VarTest) {} PropagationInfo(const VarDecl *Var, ConsumedState TestsFor) : InfoType(IT_VarTest) { VarTest.Var = Var; VarTest.TestsFor = TestsFor; } PropagationInfo(const BinaryOperator *Source, EffectiveOp EOp, const VarTestResult <est, const VarTestResult &RTest) : InfoType(IT_BinTest) { BinTest.Source = Source; BinTest.EOp = EOp; BinTest.LTest = LTest; BinTest.RTest = RTest; } PropagationInfo(const BinaryOperator *Source, EffectiveOp EOp, const VarDecl *LVar, ConsumedState LTestsFor, const VarDecl *RVar, ConsumedState RTestsFor) : InfoType(IT_BinTest) { BinTest.Source = Source; BinTest.EOp = EOp; BinTest.LTest.Var = LVar; BinTest.LTest.TestsFor = LTestsFor; BinTest.RTest.Var = RVar; BinTest.RTest.TestsFor = RTestsFor; } PropagationInfo(ConsumedState State) : InfoType(IT_State), State(State) {} PropagationInfo(const VarDecl *Var) : InfoType(IT_Var), Var(Var) {} PropagationInfo(const CXXBindTemporaryExpr *Tmp) : InfoType(IT_Tmp), Tmp(Tmp) {} const ConsumedState &getState() const { assert(InfoType == IT_State); return State; } const VarTestResult &getVarTest() const { assert(InfoType == IT_VarTest); return VarTest; } const VarTestResult &getLTest() const { assert(InfoType == IT_BinTest); return BinTest.LTest; } const VarTestResult &getRTest() const { assert(InfoType == IT_BinTest); return BinTest.RTest; } const VarDecl *getVar() const { assert(InfoType == IT_Var); return Var; } const CXXBindTemporaryExpr *getTmp() const { assert(InfoType == IT_Tmp); return Tmp; } ConsumedState getAsState(const ConsumedStateMap *StateMap) const { assert(isVar() || isTmp() || isState()); if (isVar()) return StateMap->getState(Var); else if (isTmp()) return StateMap->getState(Tmp); else if (isState()) return State; else return CS_None; } EffectiveOp testEffectiveOp() const { assert(InfoType == IT_BinTest); return BinTest.EOp; } const BinaryOperator * testSourceNode() const { assert(InfoType == IT_BinTest); return BinTest.Source; } bool isValid() const { return InfoType != IT_None; } bool isState() const { return InfoType == IT_State; } bool isVarTest() const { return InfoType == IT_VarTest; } bool isBinTest() const { return InfoType == IT_BinTest; } bool isVar() const { return InfoType == IT_Var; } bool isTmp() const { return InfoType == IT_Tmp; } bool isTest() const { return InfoType == IT_VarTest || InfoType == IT_BinTest; } bool isPointerToValue() const { return InfoType == IT_Var || InfoType == IT_Tmp; } PropagationInfo invertTest() const { assert(InfoType == IT_VarTest || InfoType == IT_BinTest); if (InfoType == IT_VarTest) { return PropagationInfo(VarTest.Var, invertConsumedUnconsumed(VarTest.TestsFor)); } else if (InfoType == IT_BinTest) { return PropagationInfo(BinTest.Source, BinTest.EOp == EO_And ? EO_Or : EO_And, BinTest.LTest.Var, invertConsumedUnconsumed(BinTest.LTest.TestsFor), BinTest.RTest.Var, invertConsumedUnconsumed(BinTest.RTest.TestsFor)); } else { return {}; } } }; } // namespace consumed } // namespace clang static void setStateForVarOrTmp(ConsumedStateMap *StateMap, const PropagationInfo &PInfo, ConsumedState State) { assert(PInfo.isVar() || PInfo.isTmp()); if (PInfo.isVar()) StateMap->setState(PInfo.getVar(), State); else StateMap->setState(PInfo.getTmp(), State); } namespace clang { namespace consumed { class ConsumedStmtVisitor : public ConstStmtVisitor { using MapType = llvm::DenseMap; using PairType= std::pair; using InfoEntry = MapType::iterator; using ConstInfoEntry = MapType::const_iterator; ConsumedAnalyzer &Analyzer; ConsumedStateMap *StateMap; MapType PropagationMap; InfoEntry findInfo(const Expr *E) { if (const auto Cleanups = dyn_cast(E)) if (!Cleanups->cleanupsHaveSideEffects()) E = Cleanups->getSubExpr(); return PropagationMap.find(E->IgnoreParens()); } ConstInfoEntry findInfo(const Expr *E) const { if (const auto Cleanups = dyn_cast(E)) if (!Cleanups->cleanupsHaveSideEffects()) E = Cleanups->getSubExpr(); return PropagationMap.find(E->IgnoreParens()); } void insertInfo(const Expr *E, const PropagationInfo &PI) { PropagationMap.insert(PairType(E->IgnoreParens(), PI)); } void forwardInfo(const Expr *From, const Expr *To); void copyInfo(const Expr *From, const Expr *To, ConsumedState CS); ConsumedState getInfo(const Expr *From); void setInfo(const Expr *To, ConsumedState NS); void propagateReturnType(const Expr *Call, const FunctionDecl *Fun); public: void checkCallability(const PropagationInfo &PInfo, const FunctionDecl *FunDecl, SourceLocation BlameLoc); bool handleCall(const CallExpr *Call, const Expr *ObjArg, const FunctionDecl *FunD); void VisitBinaryOperator(const BinaryOperator *BinOp); void VisitCallExpr(const CallExpr *Call); void VisitCastExpr(const CastExpr *Cast); void VisitCXXBindTemporaryExpr(const CXXBindTemporaryExpr *Temp); void VisitCXXConstructExpr(const CXXConstructExpr *Call); void VisitCXXMemberCallExpr(const CXXMemberCallExpr *Call); void VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *Call); void VisitDeclRefExpr(const DeclRefExpr *DeclRef); void VisitDeclStmt(const DeclStmt *DelcS); 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); ConsumedStmtVisitor(ConsumedAnalyzer &Analyzer, ConsumedStateMap *StateMap) : Analyzer(Analyzer), StateMap(StateMap) {} PropagationInfo getInfo(const Expr *StmtNode) const { ConstInfoEntry Entry = findInfo(StmtNode); if (Entry != PropagationMap.end()) return Entry->second; else return {}; } void reset(ConsumedStateMap *NewStateMap) { StateMap = NewStateMap; } }; } // namespace consumed } // namespace clang void ConsumedStmtVisitor::forwardInfo(const Expr *From, const Expr *To) { InfoEntry Entry = findInfo(From); if (Entry != PropagationMap.end()) insertInfo(To, Entry->second); } // Create a new state for To, which is initialized to the state of From. // If NS is not CS_None, sets the state of From to NS. void ConsumedStmtVisitor::copyInfo(const Expr *From, const Expr *To, ConsumedState NS) { InfoEntry Entry = findInfo(From); if (Entry != PropagationMap.end()) { PropagationInfo& PInfo = Entry->second; ConsumedState CS = PInfo.getAsState(StateMap); if (CS != CS_None) insertInfo(To, PropagationInfo(CS)); if (NS != CS_None && PInfo.isPointerToValue()) setStateForVarOrTmp(StateMap, PInfo, NS); } } // Get the ConsumedState for From ConsumedState ConsumedStmtVisitor::getInfo(const Expr *From) { InfoEntry Entry = findInfo(From); if (Entry != PropagationMap.end()) { PropagationInfo& PInfo = Entry->second; return PInfo.getAsState(StateMap); } return CS_None; } // If we already have info for To then update it, otherwise create a new entry. void ConsumedStmtVisitor::setInfo(const Expr *To, ConsumedState NS) { InfoEntry Entry = findInfo(To); if (Entry != PropagationMap.end()) { PropagationInfo& PInfo = Entry->second; if (PInfo.isPointerToValue()) setStateForVarOrTmp(StateMap, PInfo, NS); } else if (NS != CS_None) { insertInfo(To, PropagationInfo(NS)); } } void ConsumedStmtVisitor::checkCallability(const PropagationInfo &PInfo, const FunctionDecl *FunDecl, SourceLocation BlameLoc) { assert(!PInfo.isTest()); const CallableWhenAttr *CWAttr = FunDecl->getAttr(); if (!CWAttr) return; if (PInfo.isVar()) { ConsumedState VarState = StateMap->getState(PInfo.getVar()); if (VarState == CS_None || isCallableInState(CWAttr, VarState)) return; Analyzer.WarningsHandler.warnUseInInvalidState( FunDecl->getNameAsString(), PInfo.getVar()->getNameAsString(), stateToString(VarState), BlameLoc); } else { ConsumedState TmpState = PInfo.getAsState(StateMap); if (TmpState == CS_None || isCallableInState(CWAttr, TmpState)) return; Analyzer.WarningsHandler.warnUseOfTempInInvalidState( FunDecl->getNameAsString(), stateToString(TmpState), BlameLoc); } } // Factors out common behavior for function, method, and operator calls. // Check parameters and set parameter state if necessary. // Returns true if the state of ObjArg is set, or false otherwise. bool ConsumedStmtVisitor::handleCall(const CallExpr *Call, const Expr *ObjArg, const FunctionDecl *FunD) { unsigned Offset = 0; if (isa(Call) && isa(FunD)) Offset = 1; // first argument is 'this' // check explicit parameters for (unsigned Index = Offset; Index < Call->getNumArgs(); ++Index) { // Skip variable argument lists. if (Index - Offset >= FunD->getNumParams()) break; const ParmVarDecl *Param = FunD->getParamDecl(Index - Offset); QualType ParamType = Param->getType(); InfoEntry Entry = findInfo(Call->getArg(Index)); if (Entry == PropagationMap.end() || Entry->second.isTest()) continue; PropagationInfo PInfo = Entry->second; // Check that the parameter is in the correct state. if (ParamTypestateAttr *PTA = Param->getAttr()) { ConsumedState ParamState = PInfo.getAsState(StateMap); ConsumedState ExpectedState = mapParamTypestateAttrState(PTA); if (ParamState != ExpectedState) Analyzer.WarningsHandler.warnParamTypestateMismatch( Call->getArg(Index)->getExprLoc(), stateToString(ExpectedState), stateToString(ParamState)); } if (!(Entry->second.isVar() || Entry->second.isTmp())) continue; // Adjust state on the caller side. - if (isRValueRef(ParamType)) - setStateForVarOrTmp(StateMap, PInfo, consumed::CS_Consumed); - else if (ReturnTypestateAttr *RT = Param->getAttr()) + if (ReturnTypestateAttr *RT = Param->getAttr()) setStateForVarOrTmp(StateMap, PInfo, mapReturnTypestateAttrState(RT)); + else if (isRValueRef(ParamType) || isConsumableType(ParamType)) + setStateForVarOrTmp(StateMap, PInfo, consumed::CS_Consumed); else if (isPointerOrRef(ParamType) && (!ParamType->getPointeeType().isConstQualified() || isSetOnReadPtrType(ParamType))) setStateForVarOrTmp(StateMap, PInfo, consumed::CS_Unknown); } if (!ObjArg) return false; // check implicit 'self' parameter, if present InfoEntry Entry = findInfo(ObjArg); if (Entry != PropagationMap.end()) { PropagationInfo PInfo = Entry->second; checkCallability(PInfo, FunD, Call->getExprLoc()); if (SetTypestateAttr *STA = FunD->getAttr()) { if (PInfo.isVar()) { StateMap->setState(PInfo.getVar(), mapSetTypestateAttrState(STA)); return true; } else if (PInfo.isTmp()) { StateMap->setState(PInfo.getTmp(), mapSetTypestateAttrState(STA)); return true; } } else if (isTestingFunction(FunD) && PInfo.isVar()) { PropagationMap.insert(PairType(Call, PropagationInfo(PInfo.getVar(), testsFor(FunD)))); } } return false; } void ConsumedStmtVisitor::propagateReturnType(const Expr *Call, const FunctionDecl *Fun) { QualType RetType = Fun->getCallResultType(); if (RetType->isReferenceType()) RetType = RetType->getPointeeType(); if (isConsumableType(RetType)) { ConsumedState ReturnState; if (ReturnTypestateAttr *RTA = Fun->getAttr()) ReturnState = mapReturnTypestateAttrState(RTA); else ReturnState = mapConsumableAttrState(RetType); PropagationMap.insert(PairType(Call, PropagationInfo(ReturnState))); } } void ConsumedStmtVisitor::VisitBinaryOperator(const BinaryOperator *BinOp) { switch (BinOp->getOpcode()) { case BO_LAnd: case BO_LOr : { InfoEntry LEntry = findInfo(BinOp->getLHS()), REntry = findInfo(BinOp->getRHS()); VarTestResult LTest, RTest; if (LEntry != PropagationMap.end() && LEntry->second.isVarTest()) { LTest = LEntry->second.getVarTest(); } else { LTest.Var = nullptr; LTest.TestsFor = CS_None; } if (REntry != PropagationMap.end() && REntry->second.isVarTest()) { RTest = REntry->second.getVarTest(); } else { RTest.Var = nullptr; RTest.TestsFor = CS_None; } if (!(LTest.Var == nullptr && RTest.Var == nullptr)) PropagationMap.insert(PairType(BinOp, PropagationInfo(BinOp, static_cast(BinOp->getOpcode() == BO_LOr), LTest, RTest))); break; } case BO_PtrMemD: case BO_PtrMemI: forwardInfo(BinOp->getLHS(), BinOp); break; default: break; } } void ConsumedStmtVisitor::VisitCallExpr(const CallExpr *Call) { const FunctionDecl *FunDecl = Call->getDirectCallee(); if (!FunDecl) return; // Special case for the std::move function. // TODO: Make this more specific. (Deferred) if (Call->isCallToStdMove()) { copyInfo(Call->getArg(0), Call, CS_Consumed); return; } handleCall(Call, nullptr, FunDecl); propagateReturnType(Call, FunDecl); } void ConsumedStmtVisitor::VisitCastExpr(const CastExpr *Cast) { forwardInfo(Cast->getSubExpr(), Cast); } void ConsumedStmtVisitor::VisitCXXBindTemporaryExpr( const CXXBindTemporaryExpr *Temp) { InfoEntry Entry = findInfo(Temp->getSubExpr()); if (Entry != PropagationMap.end() && !Entry->second.isTest()) { StateMap->setState(Temp, Entry->second.getAsState(StateMap)); PropagationMap.insert(PairType(Temp, PropagationInfo(Temp))); } } void ConsumedStmtVisitor::VisitCXXConstructExpr(const CXXConstructExpr *Call) { CXXConstructorDecl *Constructor = Call->getConstructor(); QualType ThisType = Constructor->getThisType()->getPointeeType(); if (!isConsumableType(ThisType)) return; // FIXME: What should happen if someone annotates the move constructor? if (ReturnTypestateAttr *RTA = Constructor->getAttr()) { // TODO: Adjust state of args appropriately. ConsumedState RetState = mapReturnTypestateAttrState(RTA); PropagationMap.insert(PairType(Call, PropagationInfo(RetState))); } else if (Constructor->isDefaultConstructor()) { PropagationMap.insert(PairType(Call, PropagationInfo(consumed::CS_Consumed))); } else if (Constructor->isMoveConstructor()) { copyInfo(Call->getArg(0), Call, CS_Consumed); } else if (Constructor->isCopyConstructor()) { // Copy state from arg. If setStateOnRead then set arg to CS_Unknown. ConsumedState NS = isSetOnReadPtrType(Constructor->getThisType()) ? CS_Unknown : CS_None; copyInfo(Call->getArg(0), Call, NS); } else { // TODO: Adjust state of args appropriately. ConsumedState RetState = mapConsumableAttrState(ThisType); PropagationMap.insert(PairType(Call, PropagationInfo(RetState))); } } void ConsumedStmtVisitor::VisitCXXMemberCallExpr( const CXXMemberCallExpr *Call) { CXXMethodDecl* MD = Call->getMethodDecl(); if (!MD) return; handleCall(Call, Call->getImplicitObjectArgument(), MD); propagateReturnType(Call, MD); } void ConsumedStmtVisitor::VisitCXXOperatorCallExpr( const CXXOperatorCallExpr *Call) { const auto *FunDecl = dyn_cast_or_null(Call->getDirectCallee()); if (!FunDecl) return; if (Call->getOperator() == OO_Equal) { ConsumedState CS = getInfo(Call->getArg(1)); if (!handleCall(Call, Call->getArg(0), FunDecl)) setInfo(Call->getArg(0), CS); return; } if (const auto *MCall = dyn_cast(Call)) handleCall(MCall, MCall->getImplicitObjectArgument(), FunDecl); else handleCall(Call, Call->getArg(0), FunDecl); propagateReturnType(Call, FunDecl); } void ConsumedStmtVisitor::VisitDeclRefExpr(const DeclRefExpr *DeclRef) { if (const auto *Var = dyn_cast_or_null(DeclRef->getDecl())) if (StateMap->getState(Var) != consumed::CS_None) PropagationMap.insert(PairType(DeclRef, PropagationInfo(Var))); } void ConsumedStmtVisitor::VisitDeclStmt(const DeclStmt *DeclS) { for (const auto *DI : DeclS->decls()) if (isa(DI)) VisitVarDecl(cast(DI)); if (DeclS->isSingleDecl()) if (const auto *Var = dyn_cast_or_null(DeclS->getSingleDecl())) PropagationMap.insert(PairType(DeclS, PropagationInfo(Var))); } void ConsumedStmtVisitor::VisitMaterializeTemporaryExpr( const MaterializeTemporaryExpr *Temp) { forwardInfo(Temp->GetTemporaryExpr(), Temp); } void ConsumedStmtVisitor::VisitMemberExpr(const MemberExpr *MExpr) { forwardInfo(MExpr->getBase(), MExpr); } void ConsumedStmtVisitor::VisitParmVarDecl(const ParmVarDecl *Param) { QualType ParamType = Param->getType(); ConsumedState ParamState = consumed::CS_None; if (const ParamTypestateAttr *PTA = Param->getAttr()) ParamState = mapParamTypestateAttrState(PTA); else if (isConsumableType(ParamType)) ParamState = mapConsumableAttrState(ParamType); else if (isRValueRef(ParamType) && isConsumableType(ParamType->getPointeeType())) ParamState = mapConsumableAttrState(ParamType->getPointeeType()); else if (ParamType->isReferenceType() && isConsumableType(ParamType->getPointeeType())) ParamState = consumed::CS_Unknown; if (ParamState != CS_None) StateMap->setState(Param, ParamState); } void ConsumedStmtVisitor::VisitReturnStmt(const ReturnStmt *Ret) { ConsumedState ExpectedState = Analyzer.getExpectedReturnState(); if (ExpectedState != CS_None) { InfoEntry Entry = findInfo(Ret->getRetValue()); if (Entry != PropagationMap.end()) { ConsumedState RetState = Entry->second.getAsState(StateMap); if (RetState != ExpectedState) Analyzer.WarningsHandler.warnReturnTypestateMismatch( Ret->getReturnLoc(), stateToString(ExpectedState), stateToString(RetState)); } } StateMap->checkParamsForReturnTypestate(Ret->getBeginLoc(), Analyzer.WarningsHandler); } void ConsumedStmtVisitor::VisitUnaryOperator(const UnaryOperator *UOp) { InfoEntry Entry = findInfo(UOp->getSubExpr()); if (Entry == PropagationMap.end()) return; switch (UOp->getOpcode()) { case UO_AddrOf: PropagationMap.insert(PairType(UOp, Entry->second)); break; case UO_LNot: if (Entry->second.isTest()) PropagationMap.insert(PairType(UOp, Entry->second.invertTest())); break; default: break; } } // TODO: See if I need to check for reference types here. void ConsumedStmtVisitor::VisitVarDecl(const VarDecl *Var) { if (isConsumableType(Var->getType())) { if (Var->hasInit()) { MapType::iterator VIT = findInfo(Var->getInit()->IgnoreImplicit()); if (VIT != PropagationMap.end()) { PropagationInfo PInfo = VIT->second; ConsumedState St = PInfo.getAsState(StateMap); if (St != consumed::CS_None) { StateMap->setState(Var, St); return; } } } // Otherwise StateMap->setState(Var, consumed::CS_Unknown); } } static void splitVarStateForIf(const IfStmt *IfNode, const VarTestResult &Test, ConsumedStateMap *ThenStates, ConsumedStateMap *ElseStates) { ConsumedState VarState = ThenStates->getState(Test.Var); if (VarState == CS_Unknown) { ThenStates->setState(Test.Var, Test.TestsFor); ElseStates->setState(Test.Var, invertConsumedUnconsumed(Test.TestsFor)); } else if (VarState == invertConsumedUnconsumed(Test.TestsFor)) { ThenStates->markUnreachable(); } else if (VarState == Test.TestsFor) { ElseStates->markUnreachable(); } } static void splitVarStateForIfBinOp(const PropagationInfo &PInfo, ConsumedStateMap *ThenStates, ConsumedStateMap *ElseStates) { const VarTestResult <est = PInfo.getLTest(), &RTest = PInfo.getRTest(); ConsumedState LState = LTest.Var ? ThenStates->getState(LTest.Var) : CS_None, RState = RTest.Var ? ThenStates->getState(RTest.Var) : CS_None; if (LTest.Var) { if (PInfo.testEffectiveOp() == EO_And) { if (LState == CS_Unknown) { ThenStates->setState(LTest.Var, LTest.TestsFor); } else if (LState == invertConsumedUnconsumed(LTest.TestsFor)) { ThenStates->markUnreachable(); } else if (LState == LTest.TestsFor && isKnownState(RState)) { if (RState == RTest.TestsFor) ElseStates->markUnreachable(); else ThenStates->markUnreachable(); } } else { if (LState == CS_Unknown) { ElseStates->setState(LTest.Var, invertConsumedUnconsumed(LTest.TestsFor)); } else if (LState == LTest.TestsFor) { ElseStates->markUnreachable(); } else if (LState == invertConsumedUnconsumed(LTest.TestsFor) && isKnownState(RState)) { if (RState == RTest.TestsFor) ElseStates->markUnreachable(); else ThenStates->markUnreachable(); } } } if (RTest.Var) { if (PInfo.testEffectiveOp() == EO_And) { if (RState == CS_Unknown) ThenStates->setState(RTest.Var, RTest.TestsFor); else if (RState == invertConsumedUnconsumed(RTest.TestsFor)) ThenStates->markUnreachable(); } else { if (RState == CS_Unknown) ElseStates->setState(RTest.Var, invertConsumedUnconsumed(RTest.TestsFor)); else if (RState == RTest.TestsFor) ElseStates->markUnreachable(); } } } bool ConsumedBlockInfo::allBackEdgesVisited(const CFGBlock *CurrBlock, const CFGBlock *TargetBlock) { assert(CurrBlock && "Block pointer must not be NULL"); assert(TargetBlock && "TargetBlock pointer must not be NULL"); unsigned int CurrBlockOrder = VisitOrder[CurrBlock->getBlockID()]; for (CFGBlock::const_pred_iterator PI = TargetBlock->pred_begin(), PE = TargetBlock->pred_end(); PI != PE; ++PI) { if (*PI && CurrBlockOrder < VisitOrder[(*PI)->getBlockID()] ) return false; } return true; } void ConsumedBlockInfo::addInfo( const CFGBlock *Block, ConsumedStateMap *StateMap, std::unique_ptr &OwnedStateMap) { assert(Block && "Block pointer must not be NULL"); auto &Entry = StateMapsArray[Block->getBlockID()]; if (Entry) { Entry->intersect(*StateMap); } else if (OwnedStateMap) Entry = std::move(OwnedStateMap); else Entry = std::make_unique(*StateMap); } void ConsumedBlockInfo::addInfo(const CFGBlock *Block, std::unique_ptr StateMap) { assert(Block && "Block pointer must not be NULL"); auto &Entry = StateMapsArray[Block->getBlockID()]; if (Entry) { Entry->intersect(*StateMap); } else { Entry = std::move(StateMap); } } ConsumedStateMap* ConsumedBlockInfo::borrowInfo(const CFGBlock *Block) { assert(Block && "Block pointer must not be NULL"); assert(StateMapsArray[Block->getBlockID()] && "Block has no block info"); return StateMapsArray[Block->getBlockID()].get(); } void ConsumedBlockInfo::discardInfo(const CFGBlock *Block) { StateMapsArray[Block->getBlockID()] = nullptr; } std::unique_ptr ConsumedBlockInfo::getInfo(const CFGBlock *Block) { assert(Block && "Block pointer must not be NULL"); auto &Entry = StateMapsArray[Block->getBlockID()]; return isBackEdgeTarget(Block) ? std::make_unique(*Entry) : std::move(Entry); } bool ConsumedBlockInfo::isBackEdge(const CFGBlock *From, const CFGBlock *To) { assert(From && "From block must not be NULL"); assert(To && "From block must not be NULL"); return VisitOrder[From->getBlockID()] > VisitOrder[To->getBlockID()]; } bool ConsumedBlockInfo::isBackEdgeTarget(const CFGBlock *Block) { assert(Block && "Block pointer must not be NULL"); // Anything with less than two predecessors can't be the target of a back // edge. if (Block->pred_size() < 2) return false; unsigned int BlockVisitOrder = VisitOrder[Block->getBlockID()]; for (CFGBlock::const_pred_iterator PI = Block->pred_begin(), PE = Block->pred_end(); PI != PE; ++PI) { if (*PI && BlockVisitOrder < VisitOrder[(*PI)->getBlockID()]) return true; } return false; } void ConsumedStateMap::checkParamsForReturnTypestate(SourceLocation BlameLoc, ConsumedWarningsHandlerBase &WarningsHandler) const { for (const auto &DM : VarMap) { if (isa(DM.first)) { const auto *Param = cast(DM.first); const ReturnTypestateAttr *RTA = Param->getAttr(); if (!RTA) continue; ConsumedState ExpectedState = mapReturnTypestateAttrState(RTA); if (DM.second != ExpectedState) WarningsHandler.warnParamReturnTypestateMismatch(BlameLoc, Param->getNameAsString(), stateToString(ExpectedState), stateToString(DM.second)); } } } void ConsumedStateMap::clearTemporaries() { TmpMap.clear(); } ConsumedState ConsumedStateMap::getState(const VarDecl *Var) const { VarMapType::const_iterator Entry = VarMap.find(Var); if (Entry != VarMap.end()) return Entry->second; return CS_None; } ConsumedState ConsumedStateMap::getState(const CXXBindTemporaryExpr *Tmp) const { TmpMapType::const_iterator Entry = TmpMap.find(Tmp); if (Entry != TmpMap.end()) return Entry->second; return CS_None; } void ConsumedStateMap::intersect(const ConsumedStateMap &Other) { ConsumedState LocalState; if (this->From && this->From == Other.From && !Other.Reachable) { this->markUnreachable(); return; } for (const auto &DM : Other.VarMap) { LocalState = this->getState(DM.first); if (LocalState == CS_None) continue; if (LocalState != DM.second) VarMap[DM.first] = CS_Unknown; } } void ConsumedStateMap::intersectAtLoopHead(const CFGBlock *LoopHead, const CFGBlock *LoopBack, const ConsumedStateMap *LoopBackStates, ConsumedWarningsHandlerBase &WarningsHandler) { ConsumedState LocalState; SourceLocation BlameLoc = getLastStmtLoc(LoopBack); for (const auto &DM : LoopBackStates->VarMap) { LocalState = this->getState(DM.first); if (LocalState == CS_None) continue; if (LocalState != DM.second) { VarMap[DM.first] = CS_Unknown; WarningsHandler.warnLoopStateMismatch(BlameLoc, DM.first->getNameAsString()); } } } void ConsumedStateMap::markUnreachable() { this->Reachable = false; VarMap.clear(); TmpMap.clear(); } void ConsumedStateMap::setState(const VarDecl *Var, ConsumedState State) { VarMap[Var] = State; } void ConsumedStateMap::setState(const CXXBindTemporaryExpr *Tmp, ConsumedState State) { TmpMap[Tmp] = State; } void ConsumedStateMap::remove(const CXXBindTemporaryExpr *Tmp) { TmpMap.erase(Tmp); } bool ConsumedStateMap::operator!=(const ConsumedStateMap *Other) const { for (const auto &DM : Other->VarMap) if (this->getState(DM.first) != DM.second) return true; return false; } void ConsumedAnalyzer::determineExpectedReturnState(AnalysisDeclContext &AC, const FunctionDecl *D) { QualType ReturnType; if (const auto *Constructor = dyn_cast(D)) { ReturnType = Constructor->getThisType()->getPointeeType(); } else ReturnType = D->getCallResultType(); if (const ReturnTypestateAttr *RTSAttr = D->getAttr()) { const CXXRecordDecl *RD = ReturnType->getAsCXXRecordDecl(); if (!RD || !RD->hasAttr()) { // 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. WarningsHandler.warnReturnTypestateForUnconsumableType( RTSAttr->getLocation(), ReturnType.getAsString()); ExpectedReturnState = CS_None; } else ExpectedReturnState = mapReturnTypestateAttrState(RTSAttr); } else if (isConsumableType(ReturnType)) { if (isAutoCastType(ReturnType)) // We can auto-cast the state to the ExpectedReturnState = CS_None; // expected state. else ExpectedReturnState = mapConsumableAttrState(ReturnType); } else ExpectedReturnState = CS_None; } bool ConsumedAnalyzer::splitState(const CFGBlock *CurrBlock, const ConsumedStmtVisitor &Visitor) { std::unique_ptr FalseStates( new ConsumedStateMap(*CurrStates)); PropagationInfo PInfo; if (const auto *IfNode = dyn_cast_or_null(CurrBlock->getTerminator().getStmt())) { const Expr *Cond = IfNode->getCond(); PInfo = Visitor.getInfo(Cond); if (!PInfo.isValid() && isa(Cond)) PInfo = Visitor.getInfo(cast(Cond)->getRHS()); if (PInfo.isVarTest()) { CurrStates->setSource(Cond); FalseStates->setSource(Cond); splitVarStateForIf(IfNode, PInfo.getVarTest(), CurrStates.get(), FalseStates.get()); } else if (PInfo.isBinTest()) { CurrStates->setSource(PInfo.testSourceNode()); FalseStates->setSource(PInfo.testSourceNode()); splitVarStateForIfBinOp(PInfo, CurrStates.get(), FalseStates.get()); } else { return false; } } else if (const auto *BinOp = dyn_cast_or_null(CurrBlock->getTerminator().getStmt())) { PInfo = Visitor.getInfo(BinOp->getLHS()); if (!PInfo.isVarTest()) { if ((BinOp = dyn_cast_or_null(BinOp->getLHS()))) { PInfo = Visitor.getInfo(BinOp->getRHS()); if (!PInfo.isVarTest()) return false; } else { return false; } } CurrStates->setSource(BinOp); FalseStates->setSource(BinOp); const VarTestResult &Test = PInfo.getVarTest(); ConsumedState VarState = CurrStates->getState(Test.Var); if (BinOp->getOpcode() == BO_LAnd) { if (VarState == CS_Unknown) CurrStates->setState(Test.Var, Test.TestsFor); else if (VarState == invertConsumedUnconsumed(Test.TestsFor)) CurrStates->markUnreachable(); } else if (BinOp->getOpcode() == BO_LOr) { if (VarState == CS_Unknown) FalseStates->setState(Test.Var, invertConsumedUnconsumed(Test.TestsFor)); else if (VarState == Test.TestsFor) FalseStates->markUnreachable(); } } else { return false; } CFGBlock::const_succ_iterator SI = CurrBlock->succ_begin(); if (*SI) BlockInfo.addInfo(*SI, std::move(CurrStates)); else CurrStates = nullptr; if (*++SI) BlockInfo.addInfo(*SI, std::move(FalseStates)); return true; } void ConsumedAnalyzer::run(AnalysisDeclContext &AC) { const auto *D = dyn_cast_or_null(AC.getDecl()); if (!D) return; CFG *CFGraph = AC.getCFG(); if (!CFGraph) return; determineExpectedReturnState(AC, D); PostOrderCFGView *SortedGraph = AC.getAnalysis(); // AC.getCFG()->viewCFG(LangOptions()); BlockInfo = ConsumedBlockInfo(CFGraph->getNumBlockIDs(), SortedGraph); CurrStates = std::make_unique(); ConsumedStmtVisitor Visitor(*this, CurrStates.get()); // Add all trackable parameters to the state map. for (const auto *PI : D->parameters()) Visitor.VisitParmVarDecl(PI); // Visit all of the function's basic blocks. for (const auto *CurrBlock : *SortedGraph) { if (!CurrStates) CurrStates = BlockInfo.getInfo(CurrBlock); if (!CurrStates) { continue; } else if (!CurrStates->isReachable()) { CurrStates = nullptr; continue; } Visitor.reset(CurrStates.get()); // Visit all of the basic block's statements. for (const auto &B : *CurrBlock) { switch (B.getKind()) { case CFGElement::Statement: Visitor.Visit(B.castAs().getStmt()); break; case CFGElement::TemporaryDtor: { const CFGTemporaryDtor &DTor = B.castAs(); const CXXBindTemporaryExpr *BTE = DTor.getBindTemporaryExpr(); Visitor.checkCallability(PropagationInfo(BTE), DTor.getDestructorDecl(AC.getASTContext()), BTE->getExprLoc()); CurrStates->remove(BTE); break; } case CFGElement::AutomaticObjectDtor: { const CFGAutomaticObjDtor &DTor = B.castAs(); SourceLocation Loc = DTor.getTriggerStmt()->getEndLoc(); const VarDecl *Var = DTor.getVarDecl(); Visitor.checkCallability(PropagationInfo(Var), DTor.getDestructorDecl(AC.getASTContext()), Loc); break; } default: break; } } // TODO: Handle other forms of branching with precision, including while- // and for-loops. (Deferred) if (!splitState(CurrBlock, Visitor)) { CurrStates->setSource(nullptr); if (CurrBlock->succ_size() > 1 || (CurrBlock->succ_size() == 1 && (*CurrBlock->succ_begin())->pred_size() > 1)) { auto *RawState = CurrStates.get(); for (CFGBlock::const_succ_iterator SI = CurrBlock->succ_begin(), SE = CurrBlock->succ_end(); SI != SE; ++SI) { if (*SI == nullptr) continue; if (BlockInfo.isBackEdge(CurrBlock, *SI)) { BlockInfo.borrowInfo(*SI)->intersectAtLoopHead( *SI, CurrBlock, RawState, WarningsHandler); if (BlockInfo.allBackEdgesVisited(CurrBlock, *SI)) BlockInfo.discardInfo(*SI); } else { BlockInfo.addInfo(*SI, RawState, CurrStates); } } CurrStates = nullptr; } } if (CurrBlock == &AC.getCFG()->getExit() && D->getCallResultType()->isVoidType()) CurrStates->checkParamsForReturnTypestate(D->getLocation(), WarningsHandler); } // End of block iterator. // Delete the last existing state map. CurrStates = nullptr; WarningsHandler.emitDiagnostics(); } diff --git a/clang/test/SemaCXX/warn-consumed-analysis.cpp b/clang/test/SemaCXX/warn-consumed-analysis.cpp index 0a6aed618626..b4dddb676372 100644 --- a/clang/test/SemaCXX/warn-consumed-analysis.cpp +++ b/clang/test/SemaCXX/warn-consumed-analysis.cpp @@ -1,952 +1,979 @@ // RUN: %clang_cc1 -fsyntax-only -verify -Wconsumed -fcxx-exceptions -std=c++11 %s // TODO: Switch to using macros for the expected warnings. #define CALLABLE_WHEN(...) __attribute__ ((callable_when(__VA_ARGS__))) #define CONSUMABLE(state) __attribute__ ((consumable(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 TEST_TYPESTATE(state) __attribute__ ((test_typestate(state))) typedef decltype(nullptr) nullptr_t; template class CONSUMABLE(unconsumed) ConsumableClass { T var; public: ConsumableClass(); ConsumableClass(nullptr_t p) RETURN_TYPESTATE(consumed); ConsumableClass(T val) RETURN_TYPESTATE(unconsumed); ConsumableClass(ConsumableClass &other); ConsumableClass(ConsumableClass &&other); ConsumableClass& operator=(ConsumableClass &other); ConsumableClass& operator=(ConsumableClass &&other); ConsumableClass& operator=(nullptr_t) SET_TYPESTATE(consumed); template ConsumableClass& operator=(ConsumableClass &other); template ConsumableClass& operator=(ConsumableClass &&other); void operator()(int a) SET_TYPESTATE(consumed); void operator*() const CALLABLE_WHEN("unconsumed"); void unconsumedCall() const CALLABLE_WHEN("unconsumed"); void callableWhenUnknown() const CALLABLE_WHEN("unconsumed", "unknown"); bool isValid() const TEST_TYPESTATE(unconsumed); operator bool() const TEST_TYPESTATE(unconsumed); bool operator!=(nullptr_t) const TEST_TYPESTATE(unconsumed); bool operator==(nullptr_t) const TEST_TYPESTATE(consumed); void constCall() const; void nonconstCall(); void consume() SET_TYPESTATE(consumed); void unconsume() SET_TYPESTATE(unconsumed); }; class CONSUMABLE(unconsumed) DestructorTester { public: DestructorTester(); DestructorTester(int); + DestructorTester(nullptr_t) RETURN_TYPESTATE(unconsumed); + DestructorTester(DestructorTester &&); void operator*() CALLABLE_WHEN("unconsumed"); ~DestructorTester() CALLABLE_WHEN("consumed"); + }; +void dtByVal(DestructorTester); +void dtByValMarkUnconsumed(DestructorTester RETURN_TYPESTATE(unconsumed)); + void baf0(const ConsumableClass var); void baf1(const ConsumableClass &var); void baf2(const ConsumableClass *var); void baf3(ConsumableClass var); void baf4(ConsumableClass &var); void baf5(ConsumableClass *var); void baf6(ConsumableClass &&var); ConsumableClass returnsUnconsumed() { return ConsumableClass(); // expected-warning {{return value not in expected state; expected 'unconsumed', observed 'consumed'}} } ConsumableClass returnsConsumed() RETURN_TYPESTATE(consumed); ConsumableClass returnsConsumed() { return ConsumableClass(); } ConsumableClass returnsUnknown() RETURN_TYPESTATE(unknown); void testInitialization() { ConsumableClass var0; ConsumableClass var1 = ConsumableClass(); ConsumableClass var2(42); ConsumableClass var3(var2); // copy constructor ConsumableClass var4(var0); // copy consumed value *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'consumed' state}} *var2; *var3; *var4; // expected-warning {{invalid invocation of method 'operator*' on object 'var4' while it is in the 'consumed' state}} var0 = ConsumableClass(42); *var0; var0 = var1; *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} if (var0.isValid()) { *var0; *var1; } else { *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} } } void testDestruction() { DestructorTester D0(42), D1(42), D2; *D0; *D1; *D2; // expected-warning {{invalid invocation of method 'operator*' on object 'D2' while it is in the 'consumed' state}} D0.~DestructorTester(); // expected-warning {{invalid invocation of method '~DestructorTester' on object 'D0' while it is in the 'unconsumed' state}} return; // expected-warning {{invalid invocation of method '~DestructorTester' on object 'D0' while it is in the 'unconsumed' state}} \ expected-warning {{invalid invocation of method '~DestructorTester' on object 'D1' while it is in the 'unconsumed' state}} } +void testDestructionByVal() { + { + // both the var and the temporary are consumed: + DestructorTester D0(nullptr); + dtByVal((DestructorTester &&)D0); + } + { + // the var is consumed but the temporary isn't: + DestructorTester D1(nullptr); + dtByValMarkUnconsumed((DestructorTester &&)D1); // expected-warning {{invalid invocation of method '~DestructorTester' on a temporary object while it is in the 'unconsumed' state}} + } +} + void testTempValue() { *ConsumableClass(); // expected-warning {{invalid invocation of method 'operator*' on a temporary object while it is in the 'consumed' state}} } void testSimpleRValueRefs() { ConsumableClass var0; ConsumableClass var1(42); *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} *var1; var0 = static_cast&&>(var1); *var0; *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'consumed' state}} } void testIfStmt() { ConsumableClass var; if (var.isValid()) { *var; } else { *var; // expected-warning {{invalid invocation of method 'operator*' on object 'var' while it is in the 'consumed' state}} } if (!var.isValid()) { *var; // expected-warning {{invalid invocation of method 'operator*' on object 'var' while it is in the 'consumed' state}} } else { *var; } if (var) { // Empty } else { *var; // expected-warning {{invalid invocation of method 'operator*' on object 'var' while it is in the 'consumed' state}} } if (var != nullptr) { // Empty } else { *var; // expected-warning {{invalid invocation of method 'operator*' on object 'var' while it is in the 'consumed' state}} } if (var == nullptr) { *var; // expected-warning {{invalid invocation of method 'operator*' on object 'var' while it is in the 'consumed' state}} } else { // Empty } } void testComplexConditionals0() { ConsumableClass var0, var1, var2; if (var0 && var1) { *var0; *var1; } else { *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'consumed' state}} } if (var0 || var1) { *var0; *var1; } else { *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'consumed' state}} } if (var0 && !var1) { *var0; *var1; } else { *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'consumed' state}} } if (var0 || !var1) { *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'consumed' state}} } else { *var0; *var1; } if (!var0 && !var1) { *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'consumed' state}} } else { *var0; *var1; } if (!var0 || !var1) { *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'consumed' state}} } else { *var0; *var1; } if (!(var0 && var1)) { *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'consumed' state}} } else { *var0; *var1; } if (!(var0 || var1)) { *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'consumed' state}} } else { *var0; *var1; } if (var0 && var1 && var2) { *var0; *var1; *var2; } else { *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'consumed' state}} *var2; // expected-warning {{invalid invocation of method 'operator*' on object 'var2' while it is in the 'consumed' state}} } #if 0 // FIXME: Get this test to pass. if (var0 || var1 || var2) { *var0; *var1; *var2; } else { *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'consumed' state}} *var2; // expected-warning {{invalid invocation of method 'operator*' on object 'var2' while it is in the 'consumed' state}} } #endif } void testComplexConditionals1() { ConsumableClass var0, var1, var2; // Coerce all variables into the unknown state. baf4(var0); baf4(var1); baf4(var2); if (var0 && var1) { *var0; *var1; } else { *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'unknown' state}} *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'unknown' state}} } if (var0 || var1) { *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'unknown' state}} *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'unknown' state}} } else { *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'consumed' state}} } if (var0 && !var1) { *var0; *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'consumed' state}} } else { *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'unknown' state}} *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'unknown' state}} } if (var0 || !var1) { *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'unknown' state}} *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'unknown' state}} } else { *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} *var1; } if (!var0 && !var1) { *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'consumed' state}} } else { *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'unknown' state}} *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'unknown' state}} } if (!(var0 || var1)) { *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'consumed' state}} } else { *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'unknown' state}} *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'unknown' state}} } if (!var0 || !var1) { *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'unknown' state}} *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'unknown' state}} } else { *var0; *var1; } if (!(var0 && var1)) { *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'unknown' state}} *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'unknown' state}} } else { *var0; *var1; } if (var0 && var1 && var2) { *var0; *var1; *var2; } else { *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'unknown' state}} *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'unknown' state}} *var2; // expected-warning {{invalid invocation of method 'operator*' on object 'var2' while it is in the 'unknown' state}} } #if 0 // FIXME: Get this test to pass. if (var0 || var1 || var2) { *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'unknown' state}} *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'unknown' state}} *var2; // expected-warning {{invalid invocation of method 'operator*' on object 'var2' while it is in the 'unknown' state}} } else { *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'consumed' state}} *var2; // expected-warning {{invalid invocation of method 'operator*' on object 'var2' while it is in the 'consumed' state}} } #endif } void testStateChangeInBranch() { ConsumableClass var; // Make var enter the 'unknown' state. baf4(var); if (!var) { var = ConsumableClass(42); } *var; } void testFunctionParam(ConsumableClass param) { if (param.isValid()) { *param; } else { *param; } param = nullptr; *param; // expected-warning {{invocation of method 'operator*' on object 'param' while it is in the 'consumed' state}} } void testParamReturnTypestateCallee(bool cond, ConsumableClass &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 testRvalueRefParamReturnTypestateCallee(ConsumableClass &&Param RETURN_TYPESTATE(unconsumed)) { + Param.unconsume(); +} + void testParamReturnTypestateCaller() { ConsumableClass var; testParamReturnTypestateCallee(true, var); + testRvalueRefParamReturnTypestateCallee((ConsumableClass &&)var); *var; } void testParamTypestateCallee(ConsumableClass Param0 PARAM_TYPESTATE(consumed), ConsumableClass &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 Var0, Var1(42); testParamTypestateCallee(Var0, Var1); // expected-warning {{argument not in expected state; expected 'consumed', observed 'unconsumed'}} } void consumeFunc(ConsumableClass P PARAM_TYPESTATE(unconsumed)); struct ParamTest { static void consumeFuncStatic(ConsumableClass P PARAM_TYPESTATE(unconsumed)); void consumeFuncMeth(ConsumableClass P PARAM_TYPESTATE(unconsumed)); void operator<<(ConsumableClass P PARAM_TYPESTATE(unconsumed)); }; void operator>>(ParamTest& pt, ConsumableClass P PARAM_TYPESTATE(unconsumed)); void testFunctionParams() { // Make sure we handle the different kinds of functions. ConsumableClass P; consumeFunc(P); // expected-warning {{argument not in expected state; expected 'unconsumed', observed 'consumed'}} ParamTest::consumeFuncStatic(P); // expected-warning {{argument not in expected state; expected 'unconsumed', observed 'consumed'}} ParamTest pt; pt.consumeFuncMeth(P); // expected-warning {{argument not in expected state; expected 'unconsumed', observed 'consumed'}} pt << P; // expected-warning {{argument not in expected state; expected 'unconsumed', observed 'consumed'}} pt >> P; // expected-warning {{argument not in expected state; expected 'unconsumed', observed 'consumed'}} } void baf3(ConsumableClass var) { *var; } void baf4(ConsumableClass &var) { *var; // expected-warning {{invalid invocation of method 'operator*' on object 'var' while it is in the 'unknown' state}} } void baf6(ConsumableClass &&var) { *var; } void testCallingConventions() { ConsumableClass var(42); baf0(var); *var; baf1(var); *var; baf2(&var); *var; + + baf3(var); + *var; baf4(var); *var; // expected-warning {{invalid invocation of method 'operator*' on object 'var' while it is in the 'unknown' state}} var = ConsumableClass(42); baf5(&var); *var; // expected-warning {{invalid invocation of method 'operator*' on object 'var' while it is in the 'unknown' state}} var = ConsumableClass(42); baf6(static_cast&&>(var)); *var; // expected-warning {{invalid invocation of method 'operator*' on object 'var' while it is in the 'consumed' state}} } void testConstAndNonConstMemberFunctions() { ConsumableClass var(42); var.constCall(); *var; var.nonconstCall(); *var; } void testFunctionParam0(ConsumableClass param) { *param; } void testFunctionParam1(ConsumableClass ¶m) { *param; // expected-warning {{invalid invocation of method 'operator*' on object 'param' while it is in the 'unknown' state}} } void testReturnStates() { ConsumableClass var; var = returnsUnconsumed(); *var; var = returnsConsumed(); *var; // expected-warning {{invalid invocation of method 'operator*' on object 'var' while it is in the 'consumed' state}} } void testCallableWhen() { ConsumableClass var(42); *var; baf4(var); var.callableWhenUnknown(); } void testMoveAsignmentish() { ConsumableClass var0; ConsumableClass var1(42); *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} *var1; var0 = static_cast&&>(var1); *var0; *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'consumed' state}} var1 = ConsumableClass(42); var1 = nullptr; *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'consumed' state}} } void testConditionalMerge() { ConsumableClass var; if (var.isValid()) { // Empty } *var; // expected-warning {{invalid invocation of method 'operator*' on object 'var' while it is in the 'consumed' state}} if (var.isValid()) { // Empty } else { // Empty } *var; // expected-warning {{invalid invocation of method 'operator*' on object 'var' while it is in the 'consumed' state}} } void testSetTypestate() { ConsumableClass var(42); *var; var.consume(); *var; // expected-warning {{invalid invocation of method 'operator*' on object 'var' while it is in the 'consumed' state}} var.unconsume(); *var; } void testConsumes0() { ConsumableClass var(nullptr); *var; // expected-warning {{invalid invocation of method 'operator*' on object 'var' while it is in the 'consumed' state}} } void testConsumes1() { ConsumableClass var(42); var.unconsumedCall(); var(6); var.unconsumedCall(); // expected-warning {{invalid invocation of method 'unconsumedCall' on object 'var' while it is in the 'consumed' state}} } void testUnreachableBlock() { ConsumableClass var(42); if (var) { *var; } else { *var; } *var; } void testForLoop1() { ConsumableClass var0, var1(42); for (int i = 0; i < 10; ++i) { // expected-warning {{state of variable 'var1' must match at the entry and exit of loop}} *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} *var1; var1.consume(); *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'consumed' state}} } *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} } void testWhileLoop1() { int i = 10; ConsumableClass var0, var1(42); while (i-- > 0) { // expected-warning {{state of variable 'var1' must match at the entry and exit of loop}} *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} *var1; var1.consume(); *var1; // expected-warning {{invalid invocation of method 'operator*' on object 'var1' while it is in the 'consumed' state}} } *var0; // expected-warning {{invalid invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} } // Tests if state information is correctly discarded for certain shapes of CFGs. void testSwitchGOTO(void) { int a; LABEL0: switch (a) case 0: goto LABEL0; goto LABEL0; } typedef const int*& IntegerPointerReference; void testIsRValueRefishAndCanonicalType(IntegerPointerReference a) {} namespace ContinueICETest { bool cond1(); bool cond2(); static void foo1() { while (cond1()) { if (cond2()) continue; } } static void foo2() { while (true) { if (false) continue; } } class runtime_error { public: virtual ~runtime_error(); }; void read(bool sf) { while (sf) { if(sf) throw runtime_error(); } } } // end namespace ContinueICETest namespace StatusUseCaseTests { class CONSUMABLE(unconsumed) __attribute__((consumable_auto_cast_state)) __attribute__((consumable_set_state_on_read)) Status { int code; public: static Status OK; Status() RETURN_TYPESTATE(consumed); Status(int c) RETURN_TYPESTATE(unconsumed); Status(const Status &other); Status(Status &&other); Status& operator=(const Status &other) CALLABLE_WHEN("unknown", "consumed"); Status& operator=(Status &&other) CALLABLE_WHEN("unknown", "consumed"); bool operator==(const Status &other) const SET_TYPESTATE(consumed); bool check() const SET_TYPESTATE(consumed); void ignore() const SET_TYPESTATE(consumed); // Status& markAsChecked() { return *this; } void clear() CALLABLE_WHEN("unknown", "consumed") SET_TYPESTATE(consumed); ~Status() CALLABLE_WHEN("unknown", "consumed"); operator bool() const; // Will not consume the object. }; bool cond(); Status doSomething(); void handleStatus(const Status& s RETURN_TYPESTATE(consumed)); void handleStatusRef(Status& s); void handleStatusPtr(Status* s); void handleStatusUnmarked(const Status& s); void log(const char* msg); void fail() __attribute__((noreturn)); void checkStat(const Status& s); void testSimpleTemporaries0() { doSomething(); // expected-warning {{invalid invocation of method '~Status' on a temporary object while it is in the 'unconsumed' state}} } void testSimpleTemporaries1() { doSomething().ignore(); } void testSimpleTemporaries2() { handleStatus(doSomething()); } void testSimpleTemporaries3() { Status s = doSomething(); } // expected-warning {{invalid invocation of method '~Status' on object 's' while it is in the 'unconsumed' state}} void testTemporariesWithControlFlow(bool a) { bool b = false || doSomething(); // expected-warning {{invalid invocation of method '~Status' on a temporary object while it is in the 'unconsumed' state}} } Status testSimpleTemporariesReturn0() { return doSomething(); } Status testSimpleTemporariesReturn1() { Status s = doSomething(); return s; } void testSimpleTemporaries4() { Status s = doSomething(); s.check(); } void testSimpleTemporaries5() { Status s = doSomething(); s.clear(); // expected-warning {{invalid invocation of method 'clear' on object 's' while it is in the 'unconsumed' state}} } void testSimpleTemporaries6() { Status s1 = doSomething(); handleStatus(s1); Status s2 = doSomething(); handleStatusRef(s2); Status s3 = doSomething(); handleStatusPtr(&s3); Status s4 = doSomething(); handleStatusUnmarked(s4); } void testSimpleTemporaries7() { Status s; s = doSomething(); } // expected-warning {{invalid invocation of method '~Status' on object 's' while it is in the 'unconsumed' state}} void testTemporariesWithConditionals0() { int a; Status s = doSomething(); if (cond()) a = 0; else a = 1; } // expected-warning {{invalid invocation of method '~Status' on object 's' while it is in the 'unconsumed' state}} void testTemporariesWithConditionals1() { int a; Status s = doSomething(); if (cond()) a = 0; else a = 1; s.ignore(); } void testTemporariesWithConditionals2() { int a; Status s = doSomething(); s.ignore(); if (cond()) a = 0; else a = 1; } void testTemporariesWithConditionals3() { Status s = doSomething(); if (cond()) { s.check(); } } void testTemporariesAndConstructors0() { Status s(doSomething()); // Test the copy constructor. s.check(); } void testTemporariesAndConstructors1F() { Status s1 = doSomething(); // Test the copy constructor. Status s2 = s1; } // expected-warning {{invalid invocation of method '~Status' on object 's2' while it is in the 'unconsumed' state}} void testTemporariesAndConstructors1S() { Status s1 = doSomething(); // Test the copy constructor. Status s2(s1); s2.check(); } void testTemporariesAndConstructors2F() { // Test the move constructor. Status s1 = doSomething(); Status s2 = static_cast(s1); } // expected-warning {{invalid invocation of method '~Status' on object 's2' while it is in the 'unconsumed' state}} void testTemporariesAndConstructors2S() { // Test the move constructor. Status s1 = doSomething(); Status s2 = static_cast(s1); s2.check(); } void testTemporariesAndOperators0F() { // Test the assignment operator. Status s1 = doSomething(); Status s2; s2 = s1; } // expected-warning {{invalid invocation of method '~Status' on object 's2' while it is in the 'unconsumed' state}} void testTemporariesAndOperators0S() { // Test the assignment operator. Status s1 = doSomething(); Status s2; s2 = s1; s2.check(); } void testTemporariesAndOperators1F() { // Test the move assignment operator. Status s1 = doSomething(); Status s2; s2 = static_cast(s1); } // expected-warning {{invalid invocation of method '~Status' on object 's2' while it is in the 'unconsumed' state}} void testTemporariesAndOperators1S() { // Test the move assignment operator. Status s1 = doSomething(); Status s2; s2 = static_cast(s1); s2.check(); } void testTemporariesAndOperators2() { Status s1 = doSomething(); Status s2 = doSomething(); s1 = s2; // expected-warning {{invalid invocation of method 'operator=' on object 's1' while it is in the 'unconsumed' state}} s1.check(); s2.check(); } Status testReturnAutocast() { Status s = doSomething(); s.check(); // consume s return s; // should autocast back to unconsumed } namespace TestParens { void test3() { checkStat((doSomething())); } void test4() { Status s = (doSomething()); s.check(); } void test5() { (doSomething()).check(); } void test6() { if ((doSomething()) == Status::OK) return; } } // end namespace TestParens } // end namespace InitializerAssertionFailTest namespace std { void move(); template void move(T&&); namespace __1 { void move(); template void move(T&&); } } namespace PR18260 { class X { public: void move(); } x; void test() { x.move(); std::move(); std::move(x); std::__1::move(); std::__1::move(x); } } // end namespace PR18260