Index: include/clang/Analysis/Analyses/Consumed.h =================================================================== --- /dev/null +++ include/clang/Analysis/Analyses/Consumed.h @@ -0,0 +1,140 @@ +//===- Consumed.h ----------------------------------------------*- C++ --*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// A intra-procedural analysis for checking consumed properties. This is based, +// in part, on research on linear types. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_CONSUMED_H +#define LLVM_CLANG_CONSUMED_H + +#include "clang/AST/DeclCXX.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/StmtCXX.h" +#include "clang/Analysis/AnalysisContext.h" +#include "clang/Analysis/Analyses/PostOrderCFGView.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Sema/ConsumedWarningsHandler.h" +#include "clang/Sema/Sema.h" + +namespace clang { +namespace consumed { + + enum ConsumedState { + // No state information for the given variable. + CS_None, + + CS_Unknown, + CS_Unconsumed, + CS_Consumed + }; + + class ConsumedStateMap { + + typedef llvm::DenseMap MapType; + typedef std::pair PairType; + + protected: + + MapType Map; + + public: + /// \brief Get the consumed state of a given variable. + ConsumedState getState(const VarDecl *Var); + + /// \brief Merge this state map with another map. + void intersect(const ConsumedStateMap *Other); + + /// \brief Mark all variables as unknown. + void makeUnknown(); + + /// \brief Set the consumed state of a given variable. + void setState(const VarDecl *Var, ConsumedState State); + }; + + class ConsumedBlockInfo { + + ConsumedStateMap **StateMapsArray; + PostOrderCFGView::CFGBlockSet VisitedBlocks; + + public: + + ConsumedBlockInfo() : StateMapsArray(NULL) {} + + ConsumedBlockInfo(const CFG *CFGraph) + : StateMapsArray(new ConsumedStateMap*[CFGraph->getNumBlockIDs()]()), + VisitedBlocks(CFGraph) {} + + void addInfo(const CFGBlock *Block, ConsumedStateMap *StateMap, + bool &AlreadyOwned); + void addInfo(const CFGBlock *Block, ConsumedStateMap *StateMap); + + ConsumedStateMap* getInfo(const CFGBlock *Block); + + void markVisited(const CFGBlock *Block); + }; + + struct VarTestResult { + const VarDecl *Var; + SourceLocation Loc; + bool UnconsumedInTrueBranch; + + VarTestResult() : Var(NULL), Loc(), UnconsumedInTrueBranch(true) {} + + VarTestResult(const VarDecl *Var, SourceLocation Loc, + bool UnconsumedInTrueBranch) + : Var(Var), Loc(Loc), UnconsumedInTrueBranch(UnconsumedInTrueBranch) {} + }; + + /// A class that handles the analysis of uniqueness violations. + class ConsumedAnalyzer { + + typedef llvm::DenseMap CacheMapType; + typedef std::pair CachePairType; + + Sema &S; + + ConsumedBlockInfo BlockInfo; + ConsumedStateMap *CurrStates; + + CacheMapType ConsumableTypeCache; + + bool hasConsumableAttributes(const CXXRecordDecl *RD); + void splitState(const CFGBlock *CurrBlock, const IfStmt *Terminator); + + public: + + ConsumedWarningsHandlerBase &WarningsHandler; + + ConsumedAnalyzer(Sema &S, ConsumedWarningsHandlerBase &WarningsHandler) + : S(S), WarningsHandler(WarningsHandler) {} + + /// \brief Get a constant reference to the Sema object. + const Sema & getSema(); + + /// \brief Check to see if the type is a consumable type. + bool isConsumableType(QualType Type); + + /// \brief Check a function's CFG for consumed violations. + /// + /// We traverse the blocks in the CFG, keeping track of the state of each + /// value who's type has uniquness annotations. If methods are invoked in + /// the wrong state a warning is issued. Each block in the CFG is traversed + /// exactly once. + void run(AnalysisDeclContext &AC); + }; + + unsigned checkEnabled(DiagnosticsEngine &D); + /// \brief Check to see if a function tests an object's validity. + bool isTestingFunction(const CXXMethodDecl *MethodDecl); + +}} // end namespace clang::consumed + +#endif Index: include/clang/Basic/Attr.td =================================================================== --- include/clang/Basic/Attr.td +++ include/clang/Basic/Attr.td @@ -918,6 +918,33 @@ let TemplateDependent = 1; } +// C/C++ consumed attributes. + +def CallableWhenUnconsumed : InheritableAttr { + let Spellings = [GNU<"callable_when_unconsumed">]; + let Subjects = [CXXMethod]; +} + +def TestsUnconsumed : InheritableAttr { + let Spellings = [GNU<"tests_unconsumed">]; + let Subjects = [CXXMethod]; +} + +def Consumes : InheritableAttr { + let Spellings = [GNU<"consumes">]; + let Subjects = [CXXMethod]; +} + +def CallableAlways : InheritableAttr { + let Spellings = [GNU<"callable_always">]; + let Subjects = [CXXMethod]; +} + +def TestsConsumed : InheritableAttr { + let Spellings = [GNU<"tests_consumed">]; + let Subjects = [CXXMethod]; +} + // Type safety attributes for `void *' pointers and type tags. def ArgumentWithTypeTag : InheritableAttr { Index: include/clang/Basic/DiagnosticGroups.td =================================================================== --- include/clang/Basic/DiagnosticGroups.td +++ include/clang/Basic/DiagnosticGroups.td @@ -478,6 +478,10 @@ ThreadSafetyPrecise]>; def ThreadSafetyBeta : DiagGroup<"thread-safety-beta">; +// Uniqueness Analysis warnings +def Consumed : DiagGroup<"consumed">; +def ConsumedStrict : DiagGroup<"consumed-strict", [Consumed]>; + // Note that putting warnings in -Wall will not disable them by default. If a // warning should be active _only_ when -Wall is passed in, mark it as // DefaultIgnore in addition to putting it here. Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -2178,6 +2178,28 @@ def warn_thread_safety_beta : Warning< "Thread safety beta warning.">, InGroup, DefaultIgnore; +// Consumed warnings +def warn_use_while_consumed : Warning< + "invocation of method '%0' on object '%1' while it is in the 'consumed' " + "state">, InGroup, DefaultIgnore; +def warn_use_of_temp_while_consumed : Warning< + "invocation of method '%0' on a temporary object while it is in the " + "'consumed' state">, InGroup, DefaultIgnore; +def warn_uniqueness_attribute_wrong_decl_type : Warning< + "%0 attribute only applies to methods">, + InGroup, DefaultIgnore; + +// ConsumedStrict warnings +def warn_use_in_unknown_state : Warning< + "invocation of method '%0' on object '%1' while it is in an unknown state">, + InGroup, DefaultIgnore; +def warn_use_of_temp_in_unknown_state : Warning< + "invocation of method '%0' on a temporary object while it is in an unknown " + "state">, InGroup, DefaultIgnore; +def warn_unnecessary_test : Warning< + "unnecessary test. Variable '%0' is known to be in the '%1' state">, + InGroup, DefaultIgnore; + def warn_impcast_vector_scalar : Warning< "implicit conversion turns vector to scalar: %0 to %1">, InGroup, DefaultIgnore; Index: include/clang/Sema/AnalysisBasedWarnings.h =================================================================== --- include/clang/Sema/AnalysisBasedWarnings.h +++ include/clang/Sema/AnalysisBasedWarnings.h @@ -38,6 +38,7 @@ unsigned enableCheckFallThrough : 1; unsigned enableCheckUnreachable : 1; unsigned enableThreadSafetyAnalysis : 1; + unsigned enableConsumedAnalysis : 1; public: Policy(); void disableCheckFallThrough() { enableCheckFallThrough = 0; } Index: include/clang/Sema/ConsumedWarningsHandler.h =================================================================== --- /dev/null +++ include/clang/Sema/ConsumedWarningsHandler.h @@ -0,0 +1,98 @@ +//===- ConsumedWarningsHandler.h -------------------------------*- C++ --*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// A handler class for warnings issued by the consumed analysis. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_CONSUMED_WARNING_HANDLER_H +#define LLVM_CLANG_CONSUMED_WARNING_HANDLER_H + +#include +#include + +#include "clang/Basic/SourceLocation.h" +#include "clang/Sema/Sema.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" + +namespace clang { +namespace consumed { + + typedef SmallVector OptionalNotes; + typedef std::pair DelayedDiag; + typedef std::list DiagList; + + class ConsumedWarningsHandlerBase { + + public: + + virtual ~ConsumedWarningsHandlerBase(); + + /// \brief Emit the warnings and notes left by the analysis. + virtual void emitDiagnostics() {} + + /// Warn about unnecessary-test errors. + /// \param VariableName -- The name of the variable that holds the unique + /// value. + /// + /// \param Loc -- The SourceLocation of the unnecessary test. + virtual void warnUnnecessaryTest(StringRef VariableName, + StringRef VariableState, + SourceLocation Loc) {} + + /// Warn about use-while-consumed errors. + /// \param MethodName -- The name of the method that was incorrectly + /// invoked. + /// + /// \param VariableName -- The name of the variable that holds the unique + /// value. + /// + /// \param Loc -- The SourceLocation of the method invocation. + virtual void warnUseOfTempWhileConsumed(StringRef MethodName, + SourceLocation Loc) {} + + /// Warn about use-in-unknown-state errors. + /// \param MethodName -- The name of the method that was incorrectly + /// invoked. + /// + /// \param VariableName -- The name of the variable that holds the unique + /// value. + /// + /// \param Loc -- The SourceLocation of the method invocation. + virtual void warnUseOfTempInUnknownState(StringRef MethodName, + SourceLocation Loc) {} + + /// Warn about use-while-consumed errors. + /// \param MethodName -- The name of the method that was incorrectly + /// invoked. + /// + /// \param VariableName -- The name of the variable that holds the unique + /// value. + /// + /// \param Loc -- The SourceLocation of the method invocation. + virtual void warnUseWhileConsumed(StringRef MethodName, + StringRef VariableName, + SourceLocation Loc) {} + + /// Warn about use-in-unknown-state errors. + /// \param MethodName -- The name of the method that was incorrectly + /// invoked. + /// + /// \param VariableName -- The name of the variable that holds the unique + /// value. + /// + /// \param Loc -- The SourceLocation of the method invocation. + virtual void warnUseInUnknownState(StringRef MethodName, + StringRef VariableName, + SourceLocation Loc) {} + }; +}} // end clang::consumed + +#endif Index: lib/Analysis/CMakeLists.txt =================================================================== --- lib/Analysis/CMakeLists.txt +++ lib/Analysis/CMakeLists.txt @@ -6,6 +6,7 @@ CFGStmtMap.cpp CallGraph.cpp CocoaConventions.cpp + Consumed.cpp Dominators.cpp FormatString.cpp LiveVariables.cpp Index: lib/Analysis/Consumed.cpp =================================================================== --- /dev/null +++ lib/Analysis/Consumed.cpp @@ -0,0 +1,802 @@ +//===- Consumed.cpp --------------------------------------------*- C++ --*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// A intra-procedural analysis for checking consumed properties. This is based, +// in part, on research on linear types. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Attr.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/AST/StmtVisitor.h" +#include "clang/AST/StmtCXX.h" +#include "clang/AST/Type.h" +#include "clang/Analysis/Analyses/PostOrderCFGView.h" +#include "clang/Analysis/AnalysisContext.h" +#include "clang/Analysis/CFG.h" +#include "clang/Analysis/Analyses/Consumed.h" +#include "clang/Basic/OperatorKinds.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Sema/ConsumedWarningsHandler.h" +#include "clang/Sema/SemaDiagnostic.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/raw_ostream.h" + +// TODO: Add support for methods with CallableWhenUnconsumed. +// TODO: Mark variables as Unknown going into while- or for-loops only if they +// are referenced inside that block. (Deferred) +// TODO: Add a method(s) to identify which method calls perform what state +// transitions. (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) +// TODO: Test IsFalseVisitor with values in the unknown state. (Deferred) +// TODO: Look into combining IsFalseVisitor and TestedVarsVisitor. (Deferred) + +using namespace clang; +using namespace consumed; + +// Key method definition +ConsumedWarningsHandlerBase::~ConsumedWarningsHandlerBase() {} + +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"; + } +} + +namespace { +class ConsumedStmtVisitor : public ConstStmtVisitor { + + union PropagationUnion { + ConsumedState State; + const VarDecl *Var; + }; + + class PropagationInfo { + PropagationUnion StateOrVar; + + public: + bool IsVar; + + PropagationInfo() : IsVar(false) { + StateOrVar.State = consumed::CS_None; + } + + PropagationInfo(ConsumedState State) : IsVar(false) { + StateOrVar.State = State; + } + + PropagationInfo(const VarDecl *Var) : IsVar(true) { + StateOrVar.Var = Var; + } + + ConsumedState getState() { return StateOrVar.State; }; + + const VarDecl * getVar() { return IsVar ? StateOrVar.Var : NULL; }; + }; + + typedef llvm::DenseMap MapType; + typedef std::pair PairType; + typedef MapType::iterator InfoEntry; + + ConsumedAnalyzer &Analyzer; + ConsumedStateMap *StateMap; + MapType PropagationMap; + + void forwardInfo(const Stmt *From, const Stmt *To); + bool isLikeMoveAssignment(const CXXMethodDecl *MethodDecl); + +public: + + void Visit(const Stmt *StmtNode); + + void VisitBinaryOperator(const BinaryOperator *BinOp); + void VisitCallExpr(const CallExpr *Call); + void VisitCastExpr(const CastExpr *Cast); + 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 VisitUnaryOperator(const UnaryOperator *UOp); + void VisitVarDecl(const VarDecl *Var); + + ConsumedStmtVisitor(ConsumedAnalyzer &Analyzer, ConsumedStateMap *StateMap) : + Analyzer(Analyzer), StateMap(StateMap) {} + + void reset() { + PropagationMap.clear(); + } +}; + +void ConsumedStmtVisitor::forwardInfo(const Stmt *From, const Stmt *To) { + InfoEntry Entry = PropagationMap.find(From); + + if (Entry != PropagationMap.end()) { + PropagationMap.insert(PairType(To, PropagationInfo(Entry->second))); + } +} + +bool ConsumedStmtVisitor::isLikeMoveAssignment( + const CXXMethodDecl *MethodDecl) { + + return MethodDecl->isMoveAssignmentOperator() || + (MethodDecl->getOverloadedOperator() == OO_Equal && + MethodDecl->getNumParams() == 1 && + MethodDecl->getParamDecl(0)->getType()->isRValueReferenceType()); +} + +void ConsumedStmtVisitor::VisitBinaryOperator(const BinaryOperator *BinOp) { + switch (BinOp->getOpcode()) { + case BO_PtrMemD: + case BO_PtrMemI: + forwardInfo(BinOp->getLHS(), BinOp); + break; + + default: + break; + } +} + +void ConsumedStmtVisitor::Visit(const Stmt *StmtNode) { + ConstStmtVisitor::Visit(StmtNode); + + for (Stmt::const_child_iterator CI = StmtNode->child_begin(), + CE = StmtNode->child_end(); CI != CE; ++CI) { + + PropagationMap.erase(*CI); + } +} + +void ConsumedStmtVisitor::VisitCallExpr(const CallExpr *Call) { + if (const FunctionDecl *FunDecl = + dyn_cast_or_null(Call->getDirectCallee())) { + + // Special case for the std::move function. + // TODO: Make this more specific. (Deferred) + if (FunDecl->getNameAsString() == "move") { + InfoEntry Entry = PropagationMap.find(Call->getArg(0)); + + if (Entry != PropagationMap.end()) { + PropagationMap.insert(PairType(Call, Entry->second)); + } + + return; + } + + unsigned Offset = Call->getNumArgs() - FunDecl->getNumParams(); + + for (unsigned Index = Offset; Index < Call->getNumArgs(); ++Index) { + QualType ParamType = FunDecl->getParamDecl(Index - Offset)->getType(); + + InfoEntry Entry = PropagationMap.find(Call->getArg(Index)); + + if (Entry == PropagationMap.end() || !Entry->second.IsVar) { + continue; + } + + PropagationInfo PState = Entry->second; + + if (ParamType->isRValueReferenceType() || + (ParamType->isLValueReferenceType() && + !cast(*ParamType).isSpelledAsLValue())) { + + StateMap->setState(PState.getVar(), consumed::CS_Consumed); + + } else if (!(ParamType.isConstQualified() || + ((ParamType->isReferenceType() || + ParamType->isPointerType()) && + ParamType->getPointeeType().isConstQualified()))) { + + StateMap->setState(PState.getVar(), consumed::CS_Unknown); + } + } + } +} + +void ConsumedStmtVisitor::VisitCastExpr(const CastExpr *Cast) { + InfoEntry Entry = PropagationMap.find(Cast->getSubExpr()); + + if (Entry != PropagationMap.end()) + PropagationMap.insert(PairType(Cast, Entry->second)); +} + +void ConsumedStmtVisitor::VisitCXXConstructExpr(const CXXConstructExpr *Call) { + CXXConstructorDecl *Constructor = Call->getConstructor(); + + ASTContext &CurrContext = Analyzer.getSema().getASTContext(); + QualType ThisType = Constructor->getThisType(CurrContext)->getPointeeType(); + + if (Analyzer.isConsumableType(ThisType)) { + if (Constructor->hasAttr() || + Constructor->isDefaultConstructor()) { + + PropagationMap.insert(PairType(Call, + PropagationInfo(consumed::CS_Consumed))); + + } else if (Constructor->isMoveConstructor()) { + + PropagationInfo PState = + PropagationMap.find(Call->getArg(0))->second; + + if (PState.IsVar) { + const VarDecl* Var = PState.getVar(); + + PropagationMap.insert(PairType(Call, + PropagationInfo(StateMap->getState(Var)))); + + StateMap->setState(Var, consumed::CS_Consumed); + + } else { + PropagationMap.insert(PairType(Call, PState)); + } + + } else if (Constructor->isCopyConstructor()) { + MapType::iterator Entry = PropagationMap.find(Call->getArg(0)); + + if (Entry != PropagationMap.end()) + PropagationMap.insert(PairType(Call, Entry->second)); + + } else { + PropagationMap.insert(PairType(Call, + PropagationInfo(consumed::CS_Unconsumed))); + } + } +} + +void ConsumedStmtVisitor::VisitCXXMemberCallExpr( + const CXXMemberCallExpr *Call) { + + VisitCallExpr(Call); + + InfoEntry Entry = PropagationMap.find(Call->getCallee()->IgnoreParens()); + + if (Entry != PropagationMap.end()) { + PropagationInfo PState = Entry->second; + if (!PState.IsVar) return; + + const CXXMethodDecl *Method = Call->getMethodDecl(); + + if (Method->hasAttr()) + StateMap->setState(PState.getVar(), consumed::CS_Consumed); + else if (!Method->isConst()) + StateMap->setState(PState.getVar(), consumed::CS_Unknown); + } +} + +void ConsumedStmtVisitor::VisitCXXOperatorCallExpr( + const CXXOperatorCallExpr *Call) { + + const FunctionDecl *FunDecl = + dyn_cast_or_null(Call->getDirectCallee()); + + if (!FunDecl) return; + + if (isa(FunDecl) && + isLikeMoveAssignment(cast(FunDecl))) { + + InfoEntry LEntry = PropagationMap.find(Call->getArg(0)); + InfoEntry REntry = PropagationMap.find(Call->getArg(1)); + + PropagationInfo LPState, RPState; + + if (LEntry != PropagationMap.end() && + REntry != PropagationMap.end()) { + + LPState = LEntry->second; + RPState = REntry->second; + + if (LPState.IsVar && RPState.IsVar) { + StateMap->setState(LPState.getVar(), + StateMap->getState(RPState.getVar())); + + StateMap->setState(RPState.getVar(), consumed::CS_Consumed); + + PropagationMap.insert(PairType(Call, LPState)); + + } else if (LPState.IsVar && !RPState.IsVar) { + StateMap->setState(LPState.getVar(), RPState.getState()); + + PropagationMap.insert(PairType(Call, LPState)); + + } else if (!LPState.IsVar && RPState.IsVar) { + PropagationMap.insert(PairType(Call, + PropagationInfo(StateMap->getState(RPState.getVar())))); + + StateMap->setState(RPState.getVar(), consumed::CS_Consumed); + + } else { + PropagationMap.insert(PairType(Call, RPState)); + } + + } else if (LEntry != PropagationMap.end() && + REntry == PropagationMap.end()) { + + LPState = LEntry->second; + + if (LPState.IsVar) { + StateMap->setState(LPState.getVar(), consumed::CS_Unknown); + + PropagationMap.insert(PairType(Call, LPState)); + + } else { + PropagationMap.insert(PairType(Call, + PropagationInfo(consumed::CS_Unknown))); + } + + } else if (LEntry == PropagationMap.end() && + REntry != PropagationMap.end()) { + + RPState = REntry->second; + + if (RPState.IsVar) { + const VarDecl *Var = RPState.getVar(); + + PropagationMap.insert(PairType(Call, + PropagationInfo(StateMap->getState(Var)))); + + StateMap->setState(Var, consumed::CS_Consumed); + + } else { + PropagationMap.insert(PairType(Call, RPState)); + } + } + + } else { + + VisitCallExpr(Call); + + InfoEntry Entry = PropagationMap.find(Call->getArg(0)); + + if (Entry != PropagationMap.end()) { + + PropagationInfo PState = Entry->second; + + // TODO: When we support CallableWhenConsumed this will have to check for + // the different attributes and change the behavior bellow. + // (Deferred) + if (FunDecl->hasAttr()) { + if (PState.IsVar) { + const VarDecl *Var = PState.getVar(); + + switch (StateMap->getState(Var)) { + case CS_Consumed: + Analyzer.WarningsHandler.warnUseWhileConsumed( + FunDecl->getNameAsString(), Var->getNameAsString(), + Call->getExprLoc()); + break; + + case CS_Unknown: + Analyzer.WarningsHandler.warnUseInUnknownState( + FunDecl->getNameAsString(), Var->getNameAsString(), + Call->getExprLoc()); + break; + + default: + break; + } + + } else { + switch (PState.getState()) { + case CS_Consumed: + Analyzer.WarningsHandler.warnUseOfTempWhileConsumed( + FunDecl->getNameAsString(), Call->getExprLoc()); + break; + + case CS_Unknown: + Analyzer.WarningsHandler.warnUseOfTempInUnknownState( + FunDecl->getNameAsString(), Call->getExprLoc()); + break; + + default: + break; + } + } + } + + // Handle non-constant member operators. + if (const CXXMethodDecl *MethodDecl = + dyn_cast_or_null(FunDecl)) { + + if (!MethodDecl->isConst() && PState.IsVar) + StateMap->setState(PState.getVar(), consumed::CS_Unknown); + } + } + } +} + +void ConsumedStmtVisitor::VisitDeclRefExpr(const DeclRefExpr *DeclRef) { + if (const VarDecl *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 (DeclStmt::const_decl_iterator DI = DeclS->decl_begin(), + DE = DeclS->decl_end(); DI != DE; ++DI) { + + if (isa(*DI)) VisitVarDecl(cast(*DI)); + } + + if (DeclS->isSingleDecl()) + if (const VarDecl *Var = dyn_cast_or_null(DeclS->getSingleDecl())) + PropagationMap.insert(PairType(DeclS, PropagationInfo(Var))); +} + +void ConsumedStmtVisitor::VisitMaterializeTemporaryExpr( + const MaterializeTemporaryExpr *Temp) { + + InfoEntry Entry = PropagationMap.find(Temp->GetTemporaryExpr()); + + if (Entry != PropagationMap.end()) + PropagationMap.insert(PairType(Temp, Entry->second)); +} + +void ConsumedStmtVisitor::VisitMemberExpr(const MemberExpr *MExpr) { + forwardInfo(MExpr->getBase(), MExpr); +} + +void ConsumedStmtVisitor::VisitUnaryOperator(const UnaryOperator *UOp) { + if (UOp->getOpcode() == UO_AddrOf) { + InfoEntry Entry = PropagationMap.find(UOp->getSubExpr()); + + if (Entry != PropagationMap.end()) + PropagationMap.insert(PairType(UOp, Entry->second)); + } +} + +void ConsumedStmtVisitor::VisitVarDecl(const VarDecl *Var) { + if (Analyzer.isConsumableType(Var->getType())) { + PropagationInfo PState = + PropagationMap.find(Var->getInit())->second; + + StateMap->setState(Var, PState.IsVar ? + StateMap->getState(PState.getVar()) : PState.getState()); + } +} +} // end anonymous::ConsumedStmtVisitor + +namespace { + +// TODO: Handle variable definitions, e.g. bool valid = x.isValid(); +// if (valid) ...; (Deferred) +class TestedVarsVisitor : public RecursiveASTVisitor { + + bool Invert; + SourceLocation CurrTestLoc; + + ConsumedStateMap *StateMap; + +public: + bool IsUsefulConditional; + VarTestResult Test; + + TestedVarsVisitor(ConsumedStateMap *StateMap) : Invert(false), + StateMap(StateMap), IsUsefulConditional(false) {} + + bool VisitCallExpr(CallExpr *Call); + bool VisitDeclRefExpr(DeclRefExpr *DeclRef); + bool VisitUnaryOperator(UnaryOperator *UnaryOp); +}; + +bool TestedVarsVisitor::VisitCallExpr(CallExpr *Call) { + if (const CXXMethodDecl *Method = + dyn_cast_or_null(Call->getDirectCallee())) { + + if (isTestingFunction(Method)) { + CurrTestLoc = Call->getExprLoc(); + IsUsefulConditional = true; + return true; + } + + IsUsefulConditional = false; + } + + return false; +} + +bool TestedVarsVisitor::VisitDeclRefExpr(DeclRefExpr *DeclRef) { + if (const VarDecl *Var = dyn_cast_or_null(DeclRef->getDecl())) { + if (StateMap->getState(Var) != consumed::CS_None) { + Test = VarTestResult(Var, CurrTestLoc, !Invert); + } + + } else { + IsUsefulConditional = false; + } + + return IsUsefulConditional; +} + +bool TestedVarsVisitor::VisitUnaryOperator(UnaryOperator *UnaryOp) { + if (UnaryOp->getOpcode() == UO_LNot) { + Invert = true; + TraverseStmt(UnaryOp->getSubExpr()); + + } else { + IsUsefulConditional = false; + } + + return false; +} +} // end anonymouse::TestedVarsVisitor + +namespace clang { +namespace consumed { + +void ConsumedBlockInfo::addInfo(const CFGBlock *Block, + ConsumedStateMap *StateMap, + bool &AlreadyOwned) { + + if (VisitedBlocks.alreadySet(Block)) return; + + ConsumedStateMap *Entry = StateMapsArray[Block->getBlockID()]; + + if (Entry) { + Entry->intersect(StateMap); + + } else if (AlreadyOwned) { + StateMapsArray[Block->getBlockID()] = new ConsumedStateMap(*StateMap); + + } else { + StateMapsArray[Block->getBlockID()] = StateMap; + AlreadyOwned = true; + } +} + +void ConsumedBlockInfo::addInfo(const CFGBlock *Block, + ConsumedStateMap *StateMap) { + + if (VisitedBlocks.alreadySet(Block)) { + delete StateMap; + return; + } + + ConsumedStateMap *Entry = StateMapsArray[Block->getBlockID()]; + + if (Entry) { + Entry->intersect(StateMap); + delete StateMap; + + } else { + StateMapsArray[Block->getBlockID()] = StateMap; + } +} + +ConsumedStateMap* ConsumedBlockInfo::getInfo(const CFGBlock *Block) { + return StateMapsArray[Block->getBlockID()]; +} + +void ConsumedBlockInfo::markVisited(const CFGBlock *Block) { + VisitedBlocks.insert(Block); +} + +ConsumedState ConsumedStateMap::getState(const VarDecl *Var) { + MapType::const_iterator Entry = Map.find(Var); + + if (Entry != Map.end()) { + return Entry->second; + + } else { + return CS_None; + } +} + +void ConsumedStateMap::intersect(const ConsumedStateMap *Other) { + ConsumedState LocalState; + + for (MapType::const_iterator DMI = Other->Map.begin(), + DME = Other->Map.end(); DMI != DME; ++DMI) { + + LocalState = this->getState(DMI->first); + + if (LocalState != CS_None && LocalState != DMI->second) + setState(DMI->first, CS_Unknown); + } +} + +void ConsumedStateMap::makeUnknown(void) { + PairType Pair; + + for (MapType::const_iterator DMI = Map.begin(), DME = Map.end(); DMI != DME; + ++DMI) { + + Pair = *DMI; + + Map.erase(Pair.first); + Map.insert(PairType(Pair.first, CS_Unknown)); + } +} + +void ConsumedStateMap::setState(const VarDecl *Var, ConsumedState State) { + Map[Var] = State; +} + +const Sema & ConsumedAnalyzer::getSema(void) { + return S; +} + + +bool ConsumedAnalyzer::isConsumableType(QualType Type) { + const CXXRecordDecl *RD = + dyn_cast_or_null(Type->getAsCXXRecordDecl()); + + if (!RD) return false; + + std::pair Entry = + ConsumableTypeCache.insert(std::make_pair(RD, false)); + + if (Entry.second) + Entry.first->second = hasConsumableAttributes(RD); + + return Entry.first->second; +} + +// TODO: Walk the base classes to see if any of them are unique types. +// (Deferred) +bool ConsumedAnalyzer::hasConsumableAttributes(const CXXRecordDecl *RD) { + for (CXXRecordDecl::method_iterator MI = RD->method_begin(), + ME = RD->method_end(); MI != ME; ++MI) { + + for (Decl::attr_iterator AI = (*MI)->attr_begin(), AE = (*MI)->attr_end(); + AI != AE; ++AI) { + + switch ((*AI)->getKind()) { + case attr::CallableWhenUnconsumed: + case attr::TestsUnconsumed: + return true; + + default: + break; + } + } + } + + return false; +} + +// TODO: Handle other forms of branching with precision, including while- and +// for-loops. (Deferred) +void ConsumedAnalyzer::splitState(const CFGBlock *CurrBlock, + const IfStmt *Terminator) { + + TestedVarsVisitor Visitor(CurrStates); + Visitor.TraverseStmt(const_cast(Terminator->getCond())); + + bool HasElse = Terminator->getElse() != NULL; + + ConsumedStateMap *ElseOrMergeStates = new ConsumedStateMap(*CurrStates); + + if (Visitor.IsUsefulConditional) { + ConsumedState VarState = CurrStates->getState(Visitor.Test.Var); + + if (VarState != CS_Unknown) { + // FIXME: Make this not warn if the test is from a macro expansion. + // (Deferred) + WarningsHandler.warnUnnecessaryTest(Visitor.Test.Var->getNameAsString(), + stateToString(VarState), Visitor.Test.Loc); + } + + if (Visitor.Test.UnconsumedInTrueBranch) { + CurrStates->setState(Visitor.Test.Var, CS_Unconsumed); + if (HasElse) ElseOrMergeStates->setState(Visitor.Test.Var, CS_Consumed); + + } else { + CurrStates->setState(Visitor.Test.Var, CS_Consumed); + if (HasElse) ElseOrMergeStates->setState(Visitor.Test.Var, CS_Unconsumed); + } + } + + CFGBlock::const_succ_iterator SI = CurrBlock->succ_begin(); + + if (*SI) BlockInfo.addInfo(*SI, CurrStates); + if (*++SI) BlockInfo.addInfo(*SI, ElseOrMergeStates); +} + +void ConsumedAnalyzer::run(AnalysisDeclContext &AC) { + const FunctionDecl *D = dyn_cast_or_null(AC.getDecl()); + + if (!D) return; + + BlockInfo = ConsumedBlockInfo(AC.getCFG()); + + PostOrderCFGView *SortedGraph = AC.getAnalysis(); + + CurrStates = new ConsumedStateMap(); + + // Visit all of the function's basic blocks. + for (PostOrderCFGView::iterator I = SortedGraph->begin(), + E = SortedGraph->end(); I != E; ++I) { + + const CFGBlock *CurrBlock = *I; + BlockInfo.markVisited(CurrBlock); + + if (CurrStates == NULL) + CurrStates = BlockInfo.getInfo(CurrBlock); + + ConsumedStmtVisitor Visitor(*this, CurrStates); + + // Visit all of the basic block's statements. + for (CFGBlock::const_iterator BI = CurrBlock->begin(), + BE = CurrBlock->end(); BI != BE; ++BI) { + + if (BI->getKind() == CFGElement::Statement) + Visitor.Visit(BI->castAs().getStmt()); + } + + // TODO: Remove any variables that have reached the end of their + // lifetimes from the state map. (Deferred) + + if (const IfStmt *Terminator = + dyn_cast_or_null(CurrBlock->getTerminator().getStmt())) { + + splitState(CurrBlock, Terminator); + CurrStates = NULL; + + } else if (CurrBlock->succ_size() > 1) { + CurrStates->makeUnknown(); + + bool OwnershipTaken = false; + + for (CFGBlock::const_succ_iterator SI = CurrBlock->succ_begin(), + SE = CurrBlock->succ_end(); SI != SE; ++SI) { + + if (*SI) BlockInfo.addInfo(*SI, CurrStates, OwnershipTaken); + } + + if (!OwnershipTaken) + delete CurrStates; + + CurrStates = NULL; + + } else if (CurrBlock->succ_size() == 1 && + (*CurrBlock->succ_begin())->pred_size() > 1) { + + BlockInfo.addInfo(*CurrBlock->succ_begin(), CurrStates); + CurrStates = NULL; + } + + Visitor.reset(); + } // End of block iterator. + + // Delete the last existing state map. + delete CurrStates; + + WarningsHandler.emitDiagnostics(); +} + +unsigned checkEnabled(DiagnosticsEngine &D) { + return (unsigned) + (D.getDiagnosticLevel(diag::warn_use_while_consumed, SourceLocation()) != + DiagnosticsEngine::Ignored); +} + +bool isTestingFunction(const CXXMethodDecl *Method) { + return Method->hasAttr(); +} + +}} // end namespace clang::consumed Index: lib/Sema/AnalysisBasedWarnings.cpp =================================================================== --- lib/Sema/AnalysisBasedWarnings.cpp +++ lib/Sema/AnalysisBasedWarnings.cpp @@ -25,6 +25,7 @@ #include "clang/AST/StmtObjC.h" #include "clang/AST/StmtVisitor.h" #include "clang/Analysis/Analyses/CFGReachabilityAnalysis.h" +#include "clang/Analysis/Analyses/Consumed.h" #include "clang/Analysis/Analyses/ReachableCode.h" #include "clang/Analysis/Analyses/ThreadSafety.h" #include "clang/Analysis/Analyses/UninitializedValues.h" @@ -1241,12 +1242,8 @@ }; } - -//===----------------------------------------------------------------------===// -// -Wthread-safety -//===----------------------------------------------------------------------===// namespace clang { -namespace thread_safety { +namespace { typedef SmallVector OptionalNotes; typedef std::pair DelayedDiag; typedef std::list DiagList; @@ -1261,7 +1258,13 @@ return SM.isBeforeInTranslationUnit(left.first.first, right.first.first); } }; +}} +//===----------------------------------------------------------------------===// +// -Wthread-safety +//===----------------------------------------------------------------------===// +namespace clang { +namespace thread_safety { namespace { class ThreadSafetyReporter : public clang::thread_safety::ThreadSafetyHandler { Sema &S; @@ -1412,6 +1415,119 @@ } //===----------------------------------------------------------------------===// +// -Wconsumed +//===----------------------------------------------------------------------===// + +namespace clang { +namespace consumed { +namespace { +class ConsumedWarningsHandler : public ConsumedWarningsHandlerBase { + + Sema &S; + DiagList Warnings; + +public: + + ConsumedWarningsHandler(Sema &S) : S(S) {} + + void emitDiagnostics() { + Warnings.sort(SortDiagBySourceLocation(S.getSourceManager())); + + for (DiagList::iterator I = Warnings.begin(), E = Warnings.end(); + I != E; ++I) { + + const OptionalNotes &Notes = I->second; + S.Diag(I->first.first, I->first.second); + + for (unsigned NoteI = 0, NoteN = Notes.size(); NoteI != NoteN; ++NoteI) { + S.Diag(Notes[NoteI].first, Notes[NoteI].second); + } + } + } + + /// Warn about unnecessary-test errors. + /// \param VariableName -- The name of the variable that holds the unique + /// value. + /// + /// \param Loc -- The SourceLocation of the unnecessary test. + void warnUnnecessaryTest(StringRef VariableName, StringRef VariableState, + SourceLocation Loc) { + + PartialDiagnosticAt Warning(Loc, S.PDiag(diag::warn_unnecessary_test) << + VariableName << VariableState); + + Warnings.push_back(DelayedDiag(Warning, OptionalNotes())); + } + + /// Warn about use-while-consumed errors. + /// \param MethodName -- The name of the method that was incorrectly + /// invoked. + /// + /// \param VariableName -- The name of the variable that holds the unique + /// value. + /// + /// \param Loc -- The SourceLocation of the method invocation. + void warnUseOfTempWhileConsumed(StringRef MethodName, SourceLocation Loc) { + + PartialDiagnosticAt Warning(Loc, S.PDiag( + diag::warn_use_of_temp_while_consumed) << MethodName); + + Warnings.push_back(DelayedDiag(Warning, OptionalNotes())); + } + + /// Warn about use-in-unknown-state errors. + /// \param MethodName -- The name of the method that was incorrectly + /// invoked. + /// + /// \param VariableName -- The name of the variable that holds the unique + /// value. + /// + /// \param Loc -- The SourceLocation of the method invocation. + void warnUseOfTempInUnknownState(StringRef MethodName, SourceLocation Loc) { + + PartialDiagnosticAt Warning(Loc, S.PDiag( + diag::warn_use_of_temp_in_unknown_state) << MethodName); + + Warnings.push_back(DelayedDiag(Warning, OptionalNotes())); + } + + /// Warn about use-while-consumed errors. + /// \param MethodName -- The name of the method that was incorrectly + /// invoked. + /// + /// \param VariableName -- The name of the variable that holds the unique + /// value. + /// + /// \param Loc -- The SourceLocation of the method invocation. + void warnUseWhileConsumed(StringRef MethodName, StringRef VariableName, + SourceLocation Loc) { + + PartialDiagnosticAt Warning(Loc, S.PDiag(diag::warn_use_while_consumed) << + MethodName << VariableName); + + Warnings.push_back(DelayedDiag(Warning, OptionalNotes())); + } + + /// Warn about use-in-unknown-state errors. + /// \param MethodName -- The name of the method that was incorrectly + /// invoked. + /// + /// \param VariableName -- The name of the variable that holds the unique + /// value. + /// + /// \param Loc -- The SourceLocation of the method invocation. + void warnUseInUnknownState(StringRef MethodName, StringRef VariableName, + SourceLocation Loc) { + + PartialDiagnosticAt Warning(Loc, S.PDiag(diag::warn_use_in_unknown_state) << + MethodName << VariableName); + + Warnings.push_back(DelayedDiag(Warning, OptionalNotes())); + } +}; +}}} + +//===----------------------------------------------------------------------===// // AnalysisBasedWarnings - Worker object used by Sema to execute analysis-based // warnings on a function, method, or block. //===----------------------------------------------------------------------===// @@ -1420,6 +1536,7 @@ enableCheckFallThrough = 1; enableCheckUnreachable = 0; enableThreadSafetyAnalysis = 0; + enableConsumedAnalysis = 0; } clang::sema::AnalysisBasedWarnings::AnalysisBasedWarnings(Sema &s) @@ -1440,6 +1557,7 @@ DefaultPolicy.enableThreadSafetyAnalysis = (unsigned) (D.getDiagnosticLevel(diag::warn_double_lock, SourceLocation()) != DiagnosticsEngine::Ignored); + DefaultPolicy.enableConsumedAnalysis = consumed::checkEnabled(D); } @@ -1501,7 +1619,8 @@ // prototyping, but we need a way for analyses to say what expressions they // expect to always be CFGElements and then fill in the BuildOptions // appropriately. This is essentially a layering violation. - if (P.enableCheckUnreachable || P.enableThreadSafetyAnalysis) { + if (P.enableCheckUnreachable || P.enableThreadSafetyAnalysis || + P.enableConsumedAnalysis) { // Unreachable code analysis and thread safety require a linearized CFG. AC.getCFGBuildOptions().setAllAlwaysAdd(); } @@ -1605,6 +1724,13 @@ Reporter.emitDiagnostics(); } + // Check for violations of consumed properties. + if (P.enableConsumedAnalysis) { + consumed::ConsumedWarningsHandler WarningHandler(S); + consumed::ConsumedAnalyzer Analyzer(S, WarningHandler); + Analyzer.run(AC); + } + if (Diags.getDiagnosticLevel(diag::warn_uninit_var, D->getLocStart()) != DiagnosticsEngine::Ignored || Diags.getDiagnosticLevel(diag::warn_sometimes_uninit_var,D->getLocStart()) Index: lib/Sema/SemaDeclAttr.cpp =================================================================== --- lib/Sema/SemaDeclAttr.cpp +++ lib/Sema/SemaDeclAttr.cpp @@ -997,6 +997,85 @@ Attr.getAttributeSpellingListIndex())); } +static void handleConsumesAttr(Sema &S, Decl *D, + const AttributeList &Attr) { + assert(!Attr.isInvalid()); + if (!checkAttributeNumArgs(S, Attr, 0)) return; + + if (!(isa(D) || isa(D))) { + S.Diag(Attr.getLoc(), diag::warn_uniqueness_attribute_wrong_decl_type) << + Attr.getName(); + return; + } + + D->addAttr(::new (S.Context) + ConsumesAttr(Attr.getRange(), S.Context, + Attr.getAttributeSpellingListIndex())); +} + +static void handleCallableAlwaysAttr(Sema &S, Decl *D, + const AttributeList &Attr) { + assert(!Attr.isInvalid()); + if (!checkAttributeNumArgs(S, Attr, 0)) return; + + if (!isa(D)) { + S.Diag(Attr.getLoc(), diag::warn_uniqueness_attribute_wrong_decl_type) << + Attr.getName(); + return; + } + + D->addAttr(::new (S.Context) + CallableAlwaysAttr(Attr.getRange(), S.Context, + Attr.getAttributeSpellingListIndex())); +} + +static void handleCallableWhenUnconsumedAttr(Sema &S, Decl *D, + const AttributeList &Attr) { + assert(!Attr.isInvalid()); + if (!checkAttributeNumArgs(S, Attr, 0)) return; + + if (!isa(D)) { + S.Diag(Attr.getLoc(), diag::warn_uniqueness_attribute_wrong_decl_type) << + Attr.getName(); + return; + } + + D->addAttr(::new (S.Context) + CallableWhenUnconsumedAttr(Attr.getRange(), S.Context, + Attr.getAttributeSpellingListIndex())); +} + +static void handleTestsConsumedAttr(Sema &S, Decl *D, + const AttributeList &Attr) { + assert(!Attr.isInvalid()); + if (!checkAttributeNumArgs(S, Attr, 0)) return; + + if (!isa(D)) { + S.Diag(Attr.getLoc(), diag::warn_uniqueness_attribute_wrong_decl_type) << + Attr.getName(); + return; + } + + D->addAttr(::new (S.Context) + TestsConsumedAttr(Attr.getRange(), S.Context, + Attr.getAttributeSpellingListIndex())); +} + +static void handleTestsUnconsumedAttr(Sema &S, Decl *D, + const AttributeList &Attr) { + assert(!Attr.isInvalid()); + if (!checkAttributeNumArgs(S, Attr, 0)) return; + + if (!isa(D)) { + S.Diag(Attr.getLoc(), diag::warn_uniqueness_attribute_wrong_decl_type) << + Attr.getName(); + return; + } + + D->addAttr(::new (S.Context) + TestsUnconsumedAttr(Attr.getRange(), S.Context, + Attr.getAttributeSpellingListIndex())); +} static void handleExtVectorTypeAttr(Sema &S, Scope *scope, Decl *D, const AttributeList &Attr) { @@ -4952,6 +5031,23 @@ handleAcquiredAfterAttr(S, D, Attr); break; + // Uniqueness analysis attributes. + case AttributeList::AT_Consumes: + handleConsumesAttr(S, D, Attr); + break; + case AttributeList::AT_CallableAlways: + handleCallableAlwaysAttr(S, D, Attr); + break; + case AttributeList::AT_CallableWhenUnconsumed: + handleCallableWhenUnconsumedAttr(S, D, Attr); + break; + case AttributeList::AT_TestsConsumed: + handleTestsConsumedAttr(S, D, Attr); + break; + case AttributeList::AT_TestsUnconsumed: + handleTestsUnconsumedAttr(S, D, Attr); + break; + // Type safety attributes. case AttributeList::AT_ArgumentWithTypeTag: handleArgumentWithTypeTagAttr(S, D, Attr); Index: test/SemaCXX/warn-consumed-analysis-strict.cpp =================================================================== --- /dev/null +++ test/SemaCXX/warn-consumed-analysis-strict.cpp @@ -0,0 +1,123 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -Wconsumed-strict -std=c++11 %s + +#define CALLABLE_WHEN_UNCONSUMED __attribute__ ((callable_when_unconsumed)) +#define CONSUMES __attribute__ ((consumes)) +#define TESTS_UNCONSUMED __attribute__ ((tests_unconsumed)) + +typedef decltype(nullptr) nullptr_t; + +template +class Bar { + T var; + + public: + Bar(void); + Bar(T val); + Bar(Bar &other); + Bar(Bar &&other); + + Bar& operator=(Bar &other); + Bar& operator=(Bar &&other); + Bar& operator=(nullptr_t); + + template + Bar& operator=(Bar &other); + + template + Bar& operator=(Bar &&other); + + void operator*(void) const CALLABLE_WHEN_UNCONSUMED; + + bool isValid(void) const TESTS_UNCONSUMED; + + void constCall(void) const; + void nonconstCall(void); + + void consume(void) CONSUMES; +}; + +void baf0(Bar &var); +void baf1(Bar *var); + +void testIfStmt(void) { + Bar var; + + if (var.isValid()) { // expected-warning {{unnecessary test. Variable 'var' is known to be in the 'consumed' state}} + + // Empty + + } else { + // Empty + } +} + +void testConditionalMerge(void) { + Bar var; + + if (var.isValid()) {// expected-warning {{unnecessary test. Variable 'var' is known to be in the 'consumed' state}} + + // Empty + } + + *var; // expected-warning {{invocation of method 'operator*' on object 'var' while it is in an unknown state}} + + if (var.isValid()) { + // Empty + + } else { + // Empty + } + + *var; // expected-warning {{invocation of method 'operator*' on object 'var' while it is in an unknown state}} +} + +void testCallingConventions(void) { + Bar var(42); + + baf0(var); + *var; // expected-warning {{invocation of method 'operator*' on object 'var' while it is in an unknown state}} + + var = Bar(42); + baf1(&var); + *var; // expected-warning {{invocation of method 'operator*' on object 'var' while it is in an unknown state}} +} + +void testMoveAsignmentish(void) { + Bar var; + + var = nullptr; + *var; // expected-warning {{invocation of method 'operator*' on object 'var' while it is in an unknown state}} +} + +void testConstAndNonConstMemberFunctions(void) { + Bar var(42); + + var.constCall(); + *var; + + var.nonconstCall(); + *var; // expected-warning {{invocation of method 'operator*' on object 'var' while it is in an unknown state}} +} + +void testSimpleForLoop(void) { + Bar var; + + for (int i = 0; i < 10; ++i) { + *var; // expected-warning {{invocation of method 'operator*' on object 'var' while it is in an unknown state}} + } + + *var; // expected-warning {{invocation of method 'operator*' on object 'var' while it is in an unknown state}} +} + +void testSimpleWhileLoop(void) { + int i = 0; + + Bar var; + + while (i < 10) { + *var; // expected-warning {{invocation of method 'operator*' on object 'var' while it is in an unknown state}} + ++i; + } + + *var; // expected-warning {{invocation of method 'operator*' on object 'var' while it is in an unknown state}} +} Index: test/SemaCXX/warn-consumed-analysis.cpp =================================================================== --- /dev/null +++ test/SemaCXX/warn-consumed-analysis.cpp @@ -0,0 +1,188 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -Wconsumed -std=c++11 %s + +#define CALLABLE_WHEN_UNCONSUMED __attribute__ ((callable_when_unconsumed)) +#define CONSUMES __attribute__ ((consumes)) +#define TESTS_UNCONSUMED __attribute__ ((tests_unconsumed)) + +typedef decltype(nullptr) nullptr_t; + +template +class Bar { + T var; + + public: + Bar(void); + Bar(nullptr_t p) CONSUMES; + Bar(T val); + Bar(Bar &other); + Bar(Bar &&other); + + Bar& operator=(Bar &other); + Bar& operator=(Bar &&other); + Bar& operator=(nullptr_t); + + template + Bar& operator=(Bar &other); + + template + Bar& operator=(Bar &&other); + + void operator*(void) const CALLABLE_WHEN_UNCONSUMED; + + bool isValid(void) const TESTS_UNCONSUMED; + operator bool() const TESTS_UNCONSUMED; + + void constCall(void) const; + void nonconstCall(void); + + void consume(void) CONSUMES; +}; + +void baf0(const Bar var); +void baf1(const Bar &var); +void baf2(const Bar *var); + +void baf3(Bar &&var); + +void testInitialization(void) { + Bar var0; + Bar var1 = Bar(); + + var0 = Bar(); + + *var0; // expected-warning {{invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} + *var1; // expected-warning {{invocation of method 'operator*' on object 'var1' while it is in the 'consumed' state}} + + if (var0.isValid()) { + *var0; + *var1; // expected-warning {{invocation of method 'operator*' on object 'var1' while it is in the 'consumed' state}} + + } else { + *var0; // expected-warning {{invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} + } +} + +void testSimpleRValueRefs(void) { + Bar var0; + Bar var1(42); + + *var0; // expected-warning {{invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} + *var1; + + var0 = static_cast&&>(var1); + + *var0; + *var1; // expected-warning {{invocation of method 'operator*' on object 'var1' while it is in the 'consumed' state}} +} + +void testIfStmt(void) { + Bar var; + + if (var.isValid()) { + // Empty + + } else { + *var; // expected-warning {{invocation of method 'operator*' on object 'var' while it is in the 'consumed' state}} + } + + if (!var.isValid()) { + *var; // expected-warning {{invocation of method 'operator*' on object 'var' while it is in the 'consumed' state}} + + } else { + *var; + } + + if (var) { + // Empty + + } else { + *var; // expected-warning {{invocation of method 'operator*' on object 'var' while it is in the 'consumed' state}} + } +} + +void testCallingConventions(void) { + Bar var(42); + + baf0(var); + *var; + + baf1(var); + *var; + + baf2(&var); + *var; + + baf3(static_cast&&>(var)); + *var; // expected-warning {{invocation of method 'operator*' on object 'var' while it is in the 'consumed' state}} +} + +void testMoveAsignmentish(void) { + Bar var0; + Bar var1(42); + + *var0; // expected-warning {{invocation of method 'operator*' on object 'var0' while it is in the 'consumed' state}} + *var1; + + var0 = static_cast&&>(var1); + + *var0; + *var1; // expected-warning {{invocation of method 'operator*' on object 'var1' while it is in the 'consumed' state}} +} + +void testConditionalMerge(void) { + Bar var; + + if (var.isValid()) { + // Empty + } + + *var; + + if (var.isValid()) { + // Empty + + } else { + // Empty + } + + *var; +} + +void testConsumes0(void) { + Bar var(42); + + *var; + + var.consume(); + + *var; // expected-warning {{invocation of method 'operator*' on object 'var' while it is in the 'consumed' state}} +} + +void testConsumes1(void) { + Bar var(nullptr); + + *var; // expected-warning {{invocation of method 'operator*' on object 'var' while it is in the 'consumed' state}} +} + +void testSimpleForLoop(void) { + Bar var; + + for (int i = 0; i < 10; ++i) { + *var; + } + + *var; +} + +void testSimpleWhileLoop(void) { + int i = 0; + + Bar var; + + while (i < 10) { + *var; + ++i; + } + + *var; +} Index: test/SemaCXX/warn-consumed-parsing.cpp =================================================================== --- /dev/null +++ test/SemaCXX/warn-consumed-parsing.cpp @@ -0,0 +1,46 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -Wconsumed -std=c++11 %s + +#define CONSUMES __attribute__ ((consumes)) +#define TESTS_UNCONSUMED __attribute__ ((tests_unconsumed)) +#define CALLABLE_ALWAYS __attribute__ ((callable_always)) +#define CALLABLE_WHEN_UNCONSUMED __attribute__ ((callable_when_unconsumed)) + +class AttrTester0 { + void Consumes(void) __attribute__ ((consumes(42))); // \ + // expected-error {{attribute takes no arguments}} + bool TestsUnconsumed(void) __attribute__ ((tests_unconsumed(42))); // \ + // expected-error {{attribute takes no arguments}} + void CallableAlways(void) __attribute__ ((callable_always(42))); // \ + // expected-error {{attribute takes no arguments}} + void CallableWhenUnconsumed(void) + __attribute__ ((callable_when_unconsumed(42))); // \ + // expected-error {{attribute takes no arguments}} +}; + +int var0 CONSUMES; // \ +\\ expected-warning {{'consumes' attribute only applies to methods}} +int var1 TESTS_UNCONSUMED; // \ +\\ expected-warning {{'tests_unconsumed' attribute only applies to methods}} +int var2 CALLABLE_ALWAYS; // \ +\\ expected-warning {{'callable_always' attribute only applies to methods}} +int var3 CALLABLE_WHEN_UNCONSUMED; // \ +\\ expected-warning {{'callable_when_unconsumed' attribute only applies to methods}} + +void function0(void) CONSUMES; // \ +\\ expected-warning {{'consumes' attribute only applies to methods}} +void function0(void) TESTS_UNCONSUMED; // \ +\\ expected-warning {{'tests_unconsumed' attribute only applies to methods}} +void function0(void) CALLABLE_ALWAYS; // \ +\\ expected-warning {{'callable_always' attribute only applies to methods}} +void function0(void) CALLABLE_WHEN_UNCONSUMED; // \ +\\ expected-warning {{'callable_when_unconsumed' attribute only applies to methods}} + +class AttrTester1 { + void Consumes(void) CONSUMES; + bool TestsUnconsumed(void) TESTS_UNCONSUMED; + void CallableAlways(void) CALLABLE_ALWAYS; +}; + +class AttrTester2 { + void CallableWhenUnconsumed(void) CALLABLE_WHEN_UNCONSUMED; +};