From df7bef07eebd5c7913e8be09c62a6a470f255fd2 Mon Sep 17 00:00:00 2001 From: DeLesley Hutchins Date: Mon, 12 Aug 2013 21:20:55 +0000 Subject: [PATCH] Patch by Chris Wailes . Reviewed by delesley, dblaikie. Add the annotations and code needed to support a basic 'consumed' analysis. Summary: This new analysis is based on academic literature on linear types. It tracks the state of a value, either as unconsumed, consumed, or unknown. Methods are then annotated as CallableWhenUnconsumed, and when an annotated method is called while the value is in the 'consumed' state a warning is issued. A value may be tested in the conditional statement of an if-statement; when this occurs we know the state of the value in the different branches, and this information is added to our analysis. The code is still highly experimental, and the names of annotations or the algorithm may be subject to change. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@188206 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/Analysis/Analyses/Consumed.h | 140 +++ include/clang/Basic/Attr.td | 22 + include/clang/Basic/DiagnosticGroups.td | 4 + include/clang/Basic/DiagnosticSemaKinds.td | 22 + include/clang/Sema/AnalysisBasedWarnings.h | 1 + include/clang/Sema/ConsumedWarningsHandler.h | 98 +++ lib/Analysis/CMakeLists.txt | 1 + lib/Analysis/Consumed.cpp | 802 ++++++++++++++++++ lib/Sema/AnalysisBasedWarnings.cpp | 138 ++- lib/Sema/SemaDeclAttr.cpp | 77 ++ .../SemaCXX/warn-consumed-analysis-strict.cpp | 123 +++ test/SemaCXX/warn-consumed-analysis.cpp | 188 ++++ test/SemaCXX/warn-consumed-parsing.cpp | 29 + 13 files changed, 1639 insertions(+), 6 deletions(-) create mode 100644 include/clang/Analysis/Analyses/Consumed.h create mode 100644 include/clang/Sema/ConsumedWarningsHandler.h create mode 100644 lib/Analysis/Consumed.cpp create mode 100644 test/SemaCXX/warn-consumed-analysis-strict.cpp create mode 100644 test/SemaCXX/warn-consumed-analysis.cpp create mode 100644 test/SemaCXX/warn-consumed-parsing.cpp diff --git a/include/clang/Analysis/Analyses/Consumed.h b/include/clang/Analysis/Analyses/Consumed.h new file mode 100644 index 0000000000..2800b01d66 --- /dev/null +++ b/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(void); + + /// \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 diff --git a/include/clang/Basic/Attr.td b/include/clang/Basic/Attr.td index f592bcbb4f..f81098dc8a 100644 --- a/include/clang/Basic/Attr.td +++ b/include/clang/Basic/Attr.td @@ -918,6 +918,28 @@ def SharedLocksRequired : InheritableAttr { 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 TestsConsumed : InheritableAttr { + let Spellings = [GNU<"tests_consumed">]; + let Subjects = [CXXMethod]; +} + // Type safety attributes for `void *' pointers and type tags. def ArgumentWithTypeTag : InheritableAttr { diff --git a/include/clang/Basic/DiagnosticGroups.td b/include/clang/Basic/DiagnosticGroups.td index 93cba3edd9..ec9c8eb08c 100644 --- a/include/clang/Basic/DiagnosticGroups.td +++ b/include/clang/Basic/DiagnosticGroups.td @@ -478,6 +478,10 @@ def ThreadSafety : DiagGroup<"thread-safety", 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. diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td index d231af330b..fd1494445a 100644 --- a/include/clang/Basic/DiagnosticSemaKinds.td +++ b/include/clang/Basic/DiagnosticSemaKinds.td @@ -2178,6 +2178,28 @@ def note_found_mutex_near_match : Note<"found near match '%0'">; 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; diff --git a/include/clang/Sema/AnalysisBasedWarnings.h b/include/clang/Sema/AnalysisBasedWarnings.h index eeac97332d..432c4e23a9 100644 --- a/include/clang/Sema/AnalysisBasedWarnings.h +++ b/include/clang/Sema/AnalysisBasedWarnings.h @@ -38,6 +38,7 @@ public: unsigned enableCheckFallThrough : 1; unsigned enableCheckUnreachable : 1; unsigned enableThreadSafetyAnalysis : 1; + unsigned enableConsumedAnalysis : 1; public: Policy(); void disableCheckFallThrough() { enableCheckFallThrough = 0; } diff --git a/include/clang/Sema/ConsumedWarningsHandler.h b/include/clang/Sema/ConsumedWarningsHandler.h new file mode 100644 index 0000000000..392d781e9e --- /dev/null +++ b/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 diff --git a/lib/Analysis/CMakeLists.txt b/lib/Analysis/CMakeLists.txt index ca166669fc..deab8f1f22 100644 --- a/lib/Analysis/CMakeLists.txt +++ b/lib/Analysis/CMakeLists.txt @@ -6,6 +6,7 @@ add_clang_library(clangAnalysis CFGStmtMap.cpp CallGraph.cpp CocoaConventions.cpp + Consumed.cpp Dominators.cpp FormatString.cpp LiveVariables.cpp diff --git a/lib/Analysis/Consumed.cpp b/lib/Analysis/Consumed.cpp new file mode 100644 index 0000000000..b6a18a8181 --- /dev/null +++ b/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() { + 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() { + 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 diff --git a/lib/Sema/AnalysisBasedWarnings.cpp b/lib/Sema/AnalysisBasedWarnings.cpp index af404a5782..19fc29b907 100644 --- a/lib/Sema/AnalysisBasedWarnings.cpp +++ b/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 @@ private: }; } - -//===----------------------------------------------------------------------===// -// -Wthread-safety -//===----------------------------------------------------------------------===// namespace clang { -namespace thread_safety { +namespace { typedef SmallVector OptionalNotes; typedef std::pair DelayedDiag; typedef std::list DiagList; @@ -1261,7 +1258,13 @@ struct SortDiagBySourceLocation { 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; @@ -1411,6 +1414,119 @@ class ThreadSafetyReporter : public clang::thread_safety::ThreadSafetyHandler { } } +//===----------------------------------------------------------------------===// +// -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 @@ clang::sema::AnalysisBasedWarnings::Policy::Policy() { enableCheckFallThrough = 1; enableCheckUnreachable = 0; enableThreadSafetyAnalysis = 0; + enableConsumedAnalysis = 0; } clang::sema::AnalysisBasedWarnings::AnalysisBasedWarnings(Sema &s) @@ -1440,6 +1557,7 @@ clang::sema::AnalysisBasedWarnings::AnalysisBasedWarnings(Sema &s) DefaultPolicy.enableThreadSafetyAnalysis = (unsigned) (D.getDiagnosticLevel(diag::warn_double_lock, SourceLocation()) != DiagnosticsEngine::Ignored); + DefaultPolicy.enableConsumedAnalysis = consumed::checkEnabled(D); } @@ -1501,7 +1619,8 @@ AnalysisBasedWarnings::IssueWarnings(sema::AnalysisBasedWarnings::Policy P, // 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 @@ AnalysisBasedWarnings::IssueWarnings(sema::AnalysisBasedWarnings::Policy P, 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()) diff --git a/lib/Sema/SemaDeclAttr.cpp b/lib/Sema/SemaDeclAttr.cpp index cae855e006..77b242d19f 100644 --- a/lib/Sema/SemaDeclAttr.cpp +++ b/lib/Sema/SemaDeclAttr.cpp @@ -997,6 +997,69 @@ static void handleLocksExcludedAttr(Sema &S, Decl *D, 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 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 +5015,20 @@ static void ProcessInheritableDeclAttr(Sema &S, Scope *scope, Decl *D, handleAcquiredAfterAttr(S, D, Attr); break; + // Uniqueness analysis attributes. + case AttributeList::AT_Consumes: + handleConsumesAttr(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); diff --git a/test/SemaCXX/warn-consumed-analysis-strict.cpp b/test/SemaCXX/warn-consumed-analysis-strict.cpp new file mode 100644 index 0000000000..0190359dc0 --- /dev/null +++ b/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}} +} diff --git a/test/SemaCXX/warn-consumed-analysis.cpp b/test/SemaCXX/warn-consumed-analysis.cpp new file mode 100644 index 0000000000..fc86317465 --- /dev/null +++ b/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; +} diff --git a/test/SemaCXX/warn-consumed-parsing.cpp b/test/SemaCXX/warn-consumed-parsing.cpp new file mode 100644 index 0000000000..23df1d19df --- /dev/null +++ b/test/SemaCXX/warn-consumed-parsing.cpp @@ -0,0 +1,29 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -Wconsumed -std=c++11 %s + +#define CONSUMES __attribute__ ((consumes)) +#define TESTS_UNCONSUMED __attribute__ ((tests_unconsumed)) +#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 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_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 function1(void) TESTS_UNCONSUMED; // expected-warning {{'tests_unconsumed' attribute only applies to methods}} +void function2(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; +}; + +class AttrTester2 { + void callableWhenUnconsumed(void) CALLABLE_WHEN_UNCONSUMED; +}; -- 2.50.1