Index: include/clang/Analysis/Analyses/Consumed.h =================================================================== --- /dev/null +++ include/clang/Analysis/Analyses/Consumed.h @@ -0,0 +1,218 @@ +//===- 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_UNIQUENESS_H +#define LLVM_CLANG_UNIQUENESS_H + +#include "clang/AST/DeclCXX.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/StmtCXX.h" +#include "clang/Analysis/AnalysisContext.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Sema/Sema.h" +#include "llvm/ADT/StringRef.h" + +namespace clang { +namespace consumed { + + /////////// + // Types // + /////////// + + typedef SmallVector OptionalNotes; + typedef std::pair DelayedDiag; + typedef std::list DiagList; + + enum ConsumedState { + // No state information for the given variable. + None, + + Unknown, + Unconsumed, + Consumed + }; + + ///////////////////////// + // Structs and Classes // + ///////////////////////// + + class ConsumedStateMap { + + /////////// + // Types // + /////////// + + typedef llvm::DenseMap MapType; + typedef std::pair PairType; + + protected: + + ////////////////// + // Data Members // + ////////////////// + + MapType Map; + + //////////////////// + // Public Methods // + //////////////////// + + public: + ConsumedStateMap(void) {} + ConsumedStateMap(ConsumedStateMap &Other) : Map(Other.Map) {} + + /// \brief Get the consumed state of a given variable. + ConsumedState getState(const VarDecl *Var); + + /// \brief Mark all variables as unknown. + void makeUnknown(void); + + /// \brief Merge this state map with another map. + void merge(const ConsumedStateMap *Other); + + /// \brief Set the consumed state of a given variable. + void setState(const VarDecl *Var, ConsumedState State); + }; + + class ConsumedBlockInfo { + + /////////// + // Types // + /////////// + + typedef llvm::DenseMap MapType; + typedef std::pair PairType; + + ////////////////// + // Data Members // + ////////////////// + + MapType Map; + + public: + + //////////////////// + // Public Methods // + //////////////////// + + void addInfo(const CFGBlock *Block, ConsumedStateMap *StateMap); + + ConsumedStateMap* getInfo(const CFGBlock *Block); + }; + + struct VarTestResult { + const VarDecl *Var; + SourceLocation Loc; + bool UnconsumedInTrueBranch; + + VarTestResult(void) : 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 { + + /////////// + // Types // + /////////// + + typedef llvm::DenseMap CacheMapType; + typedef std::pair CachePairType; + + ////////////////// + // Data Members // + ////////////////// + + Sema &S; + + DiagList Warnings; + SourceLocation FL, FEL; + + ConsumedBlockInfo BlockInfo; + ConsumedStateMap *CurrStates; + + CacheMapType ConsumableTypeCache; + + ///////////////////// + // Private Methods // + ///////////////////// + + /// \brief Emit the warnings and notes left by the analysis. + void emitDiagnostics(void); + + // Tests + + bool isConsumableType(QualType Type); + + // End tests + + void splitState(const CFGBlock *CurrBlock, const IfStmt *Terminator); + + public: + + ////////////////// + // Constructors // + ////////////////// + + ConsumedAnalyzer(Sema &S) : S(S) {} + + //////////////////// + // Public Methods // + //////////////////// + + /// \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); + + // Warning Methods + + /// 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); + + /// 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); + + /// 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); + }; + + ////////////////////// + // Module Functions // + ////////////////////// + + 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 @@ -913,6 +913,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 @@ -475,6 +475,9 @@ ThreadSafetyPrecise]>; def ThreadSafetyBeta : DiagGroup<"thread-safety-beta">; +// Uniqueness Analysis warnings +def Consumed : DiagGroup<"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 @@ -2170,6 +2170,21 @@ InGroup, DefaultIgnore; def note_found_mutex_near_match : Note<"found near match '%0'">; +// Uniqueness 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_in_unknown_state : Warning< + "Invocation of method '%0' on object '%1' 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_uniqueness_attribute_wrong_decl_type : Warning< + "%0 attribute only applies to methods">, + InGroup, DefaultIgnore; + // Dummy warning that will trigger "beta" warnings from the analysis if enabled. def warn_thread_safety_beta : Warning< "Thread safety beta warning.">, 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: 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,648 @@ +//===- 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/Attr.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/RecursiveASTVisitor.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/Basic/SourceManager.h" +#include "clang/Sema/SemaDiagnostic.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang; +using namespace consumed; + +// TODO: Add tests cases. +// TODO: Test to make sure merging is happening correctly. One branch should +// make the value consumed, and one should make it unconsumed. Make sure +// it is in an unknown state, and then try and dereference it. +// TODO: Mark variables as Consumed going into while- or for-loops only if they +// are referenced inside that block. (Deferred) +// TODO: Support CONSUME methods. (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) + +////////////////////// +// Helper Functions // +////////////////////// + +static StringRef stateToString(ConsumedState State) { + switch (State) { + case consumed::None: + return "none"; + + case consumed::Unknown: + return "unknown"; + + case consumed::Unconsumed: + return "unconsumed"; + + case consumed::Consumed: + return "consumed"; + } +} + +///////////////////////// +// ConsumedStmtVisitor // +///////////////////////// + +namespace { +class ConsumedStmtVisitor + : public RecursiveASTVisitor { + + enum PropagationMode { + Disabled, + Set, + Consume, + ConsumeAndPropagate + }; + + llvm::SmallVector ModeStack; + + ConsumedAnalyzer &Analyzer; + ConsumedStateMap *StateMap; + ConsumedState PropagatingState; + +public: + + bool VisitCallExpr(CallExpr *Call); + bool VisitCXXConstructExpr(CXXConstructExpr *Call); + bool VisitCXXOperatorCallExpr(CXXOperatorCallExpr *Call); + bool VisitDeclRefExpr(DeclRefExpr *DeclRef); + bool VisitVarDecl(VarDecl *Var); + + ConsumedStmtVisitor(ConsumedAnalyzer &Analyzer, + ConsumedStateMap *StateMap) : Analyzer(Analyzer), StateMap(StateMap) { + ModeStack.push_back(Disabled); + } + + void reset(void) { + ModeStack.clear(); + ModeStack.push_back(Disabled); + } +}; + +bool ConsumedStmtVisitor::VisitCallExpr(CallExpr *Call) { + // FIXME: Should this be a different kind of ignore? + if (const CastExpr *Cast = + dyn_cast(Call->getCallee()->IgnoreImpCasts())) { + + if (Cast->getCastKind() == CK_LValueToRValue) { + + ModeStack.push_back(ConsumeAndPropagate); + TraverseStmt(Call->getArg(0)); + ModeStack.pop_back(); + + return false; + } + + return true; + + } else if (const FunctionDecl *FunDecl = + dyn_cast_or_null(Call->getCalleeDecl())) { + + if (const CXXMethodDecl *MethodDecl = + dyn_cast_or_null(FunDecl)) { + + if (MethodDecl->isMoveAssignmentOperator()) { + + ModeStack.push_back(ConsumeAndPropagate); + TraverseStmt(Call->getArg(1)); + + ModeStack.back() = Set; + TraverseStmt(Call->getArg(0)); + + ModeStack.pop_back(); + + return false; + } + } + + unsigned NumArgParamPairs = + std::min(Call->getNumArgs(), FunDecl->getNumParams()); + + ModeStack.push_back(Disabled); + for (unsigned Index = 0; Index < NumArgParamPairs; ++Index) { + if (FunDecl->getParamDecl(Index)->getType()->isRValueReferenceType()) { + ModeStack.back() = Consume; + } else { + ModeStack.back() = Disabled; + } + + TraverseStmt(Call->getArg(Index)); + } + ModeStack.pop_back(); + } + + return true; +} + +bool ConsumedStmtVisitor::VisitCXXConstructExpr(CXXConstructExpr *Call) { + const CXXConstructorDecl *ConDecl = Call->getConstructor(); + + if (ConDecl->isDefaultConstructor()) { + PropagatingState = Consumed; + + } else if (ConDecl->isMoveConstructor()) { + + ModeStack.push_back(ConsumeAndPropagate); + TraverseStmt(Call->getArg(0)); + ModeStack.pop_back(); + + } else { + PropagatingState = Unconsumed; + } + + return false; +} + +// TODO: Copy this block for CXXMemberCallExpr. (Deferred) +bool ConsumedStmtVisitor::VisitCXXOperatorCallExpr( + CXXOperatorCallExpr *Call) { + + if (const FunctionDecl *FunDecl = + dyn_cast_or_null(Call->getDirectCallee())) { + + // TODO: When we support CallableAlways (or CallableWhenConsumed) this will + // have to check for the different attributes and change the behavior + // bellow. (Deferred) + if (!FunDecl->hasAttr()) return false; + + if (const DeclRefExpr *DeclRef = + dyn_cast_or_null(Call->getArg(0))) { + + if (const VarDecl *Var = dyn_cast_or_null(DeclRef->getDecl())) { + switch (StateMap->getState(Var)) { + case Consumed: + Analyzer.warnUseWhileConsumed(FunDecl->getNameAsString(), + Var->getNameAsString(), Call->getExprLoc()); + break; + + case Unknown: + Analyzer.warnUseInUnknownState(FunDecl->getNameAsString(), + Var->getNameAsString(), Call->getExprLoc()); + break; + + default: + break; + } + } + } + } + + return false; +} + +bool ConsumedStmtVisitor::VisitDeclRefExpr(DeclRefExpr *DeclRef) { + if (const VarDecl *Var = dyn_cast(DeclRef->getDecl())) { + + // Do nothing if we aren't tracking the state of this variable. + if (StateMap->getState(Var) == consumed::None) return true; + + switch (ModeStack.back()) { + case Disabled: + break; + + case Set: + StateMap->setState(Var, PropagatingState); + break; + + case ConsumeAndPropagate: + PropagatingState = StateMap->getState(Var); + + case Consume: + StateMap->setState(Var, Consumed); + break; + } + } + + return true; +} + +// TODO: Handle the case where a var decl happens in sub expressions. (Deferred) +bool ConsumedStmtVisitor::VisitVarDecl(VarDecl *Var) { + TraverseStmt(const_cast(Var->getInit())); + StateMap->setState(Var, PropagatingState); + return false; +} +} + +////////////////////////////// +// SortDiagBySourceLocation // +////////////////////////////// + +namespace { +struct SortDiagBySourceLocation { + SourceManager &SM; + SortDiagBySourceLocation(SourceManager &SM) : SM(SM) {} + + bool operator()(const DelayedDiag &Left, const DelayedDiag &Right); +}; + +bool SortDiagBySourceLocation::operator()(const DelayedDiag &Left, + const DelayedDiag &Right) { + + // Although this call will be slow, this is only called when outputting + // multiple warnings. + return SM.isBeforeInTranslationUnit(Left.first.first, Right.first.first); +} +} + +/////////////////////// +// TestedVarsVisitor // +/////////////////////// + +namespace { +class TestedVarsVisitor : public RecursiveASTVisitor { + + bool Invert; + SourceLocation CurrTestLoc; + + ConsumedStateMap *StateMap; + +public: + bool IsUsefulConditional; + VarTestResult Test; + + TestedVarsVisitor(ConsumedStateMap *StateMap) : Invert(false), + StateMap(StateMap), IsUsefulConditional(true) {} + + 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(); + 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::None) { + Test = VarTestResult(Var, CurrTestLoc, !Invert); + } + + } else { + IsUsefulConditional = false; + } + + return IsUsefulConditional; +} + +bool TestedVarsVisitor::VisitUnaryOperator(UnaryOperator *UnaryOp) { + if (UnaryOp->getOpcode() == UO_Not) { + Invert = true; + TraverseStmt(UnaryOp->getSubExpr()); + + } else { + IsUsefulConditional = false; + } + + return false; +} +} + +namespace clang { +namespace consumed { + +/////////////////////// +// ConsumedBlockInfo // +/////////////////////// + +void ConsumedBlockInfo::addInfo(const CFGBlock *Block, + ConsumedStateMap *StateMap) { + + MapType::iterator Entry = Map.find(Block); + + if (Entry == Map.end()) { + // No info for this block is currently in the map. + Map.insert(PairType(Block, StateMap)); + + } else { + // Merge the new state map with the existing one, and then free the new map. + (*Entry).second->merge(StateMap); + delete StateMap; + } +} + +ConsumedStateMap* ConsumedBlockInfo::getInfo(const CFGBlock *Block) { + ConsumedStateMap *RetValue = (*Map.find(Block)).second; + + Map.erase(Block); + + return RetValue; +} + +////////////////////// +// ConsumedStateMap // +////////////////////// + +ConsumedState ConsumedStateMap::getState(const VarDecl *Var) { + MapType::const_iterator Entry = Map.find(Var); + + if (Entry != Map.end()) { + return (*Entry).second; + + } else { + return None; + } +} + +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, Unknown)); + } +} + +void ConsumedStateMap::merge(const ConsumedStateMap *Other) { + ConsumedState LocalState; + + for (MapType::const_iterator DMI = Other->Map.begin(), + DME = Other->Map.end(); DMI != DME; ++DMI) { + + PairType OtherPair = *DMI; + + LocalState = this->getState(OtherPair.first); + + if (LocalState == None) { + Map.insert(OtherPair); + + } else if (LocalState != OtherPair.second) { + Map.erase(OtherPair.first); + Map.insert(PairType(OtherPair.first, Unknown)); + } + } + + llvm::errs() << "\n"; +} + +void ConsumedStateMap::setState(const VarDecl *Var, ConsumedState State) { + Map.erase(Var); + Map.insert(PairType(Var, State)); +} + +////////////////////// +// ConsumedAnalyzer // +////////////////////// + +/// \brief Emit all buffered diagnostics in order of sourcelocation. +/// We need to output diagnostics produced while checking operations on +/// unique types in deterministic order, so this function orders diagnostics +/// and outputs them. +void ConsumedAnalyzer::emitDiagnostics(void) { + 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); + } + } +} + +// TODO: Walk the base classes to see if any of them are unique types. +// (Deferred) +bool ConsumedAnalyzer::isConsumableType(QualType Type) { + if (const CXXRecordDecl *Klass = + dyn_cast_or_null(Type->getAsCXXRecordDecl())) { + + CacheMapType::const_iterator Entry = ConsumableTypeCache.find(Klass); + + if (Entry != ConsumableTypeCache.end()) { + return (*Entry).second; + } + + for (CXXRecordDecl::method_iterator MI = Klass->method_begin(), + ME = Klass->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: + ConsumableTypeCache.insert(CachePairType(Klass, true)); + return true; + + default: + break; + } + } + } + + ConsumableTypeCache.insert(CachePairType(Klass, false)); + } + + return false; +} + +// FIXME: Make this not generate false positives for while- and for-loops. +// 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 != Unknown) { + warnUnnecessaryTest(Visitor.Test.Var->getNameAsString(), + stateToString(VarState), Visitor.Test.Loc); + } + + if (Visitor.Test.UnconsumedInTrueBranch) { + CurrStates->setState(Visitor.Test.Var, Unconsumed); + + if (HasElse) ElseOrMergeStates->setState(Visitor.Test.Var, Consumed); + + } else { + CurrStates->setState(Visitor.Test.Var, Consumed); + + if (HasElse) ElseOrMergeStates->setState(Visitor.Test.Var, Unconsumed); + } + } + + CFGBlock::const_succ_iterator SI = CurrBlock->succ_begin(); + + BlockInfo.addInfo( *SI, CurrStates); + BlockInfo.addInfo(*++SI, ElseOrMergeStates); +} + +void ConsumedAnalyzer::run(AnalysisDeclContext &AC) { + const FunctionDecl *D = dyn_cast_or_null(AC.getDecl()); + + if (!D) { + return; + } + + FL = AC.getDecl()->getLocation(); + FEL = AC.getDecl()->getLocEnd(); + + 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; + + 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.TraverseStmt(const_cast( + BI->castAs().getStmt())); + + Visitor.reset(); + } + } + + // 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() == 2) { + // Handle while- and for-loops. + CurrStates->makeUnknown(); + ConsumedStateMap *StatesCopy = new ConsumedStateMap(*CurrStates); + + CFGBlock::const_succ_iterator SI = CurrBlock->succ_begin(); + + BlockInfo.addInfo( *SI, CurrStates); + BlockInfo.addInfo(*++SI, StatesCopy); + + CurrStates = NULL; + + } else if (CurrBlock->succ_size() == 1 && + (*CurrBlock->succ_begin())->pred_size() > 1) { + + BlockInfo.addInfo(*CurrBlock->succ_begin(), CurrStates); + CurrStates = NULL; + } + } // End of block iterator. + + // Delete the last existing state map. + delete CurrStates; + + emitDiagnostics(); +} + +void ConsumedAnalyzer::warnUnnecessaryTest(StringRef VariableName, + StringRef VariableState, + SourceLocation Loc) { + + PartialDiagnosticAt Warning(Loc, S.PDiag(diag::warn_unnecessary_test) << + VariableName << VariableState); + + Warnings.push_back(DelayedDiag(Warning, OptionalNotes())); +} + +void ConsumedAnalyzer::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())); +} + +void ConsumedAnalyzer::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())); +} + +////////////////////// +// Module Functions // +////////////////////// + +unsigned checkEnabled(DiagnosticsEngine &D) { + return (unsigned) + (D.getDiagnosticLevel(diag::warn_use_while_consumed, SourceLocation()) != + DiagnosticsEngine::Ignored); +} + +bool isTestingFunction(const CXXMethodDecl *Method) { + if (Method->hasAttr()) { + return true; + } else { + return false; + } +} + +}} // 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" @@ -1420,6 +1421,7 @@ enableCheckFallThrough = 1; enableCheckUnreachable = 0; enableThreadSafetyAnalysis = 0; + enableConsumedAnalysis = 0; } clang::sema::AnalysisBasedWarnings::AnalysisBasedWarnings(Sema &s) @@ -1440,6 +1442,7 @@ DefaultPolicy.enableThreadSafetyAnalysis = (unsigned) (D.getDiagnosticLevel(diag::warn_double_lock, SourceLocation()) != DiagnosticsEngine::Ignored); + DefaultPolicy.enableConsumedAnalysis = consumed::checkEnabled(D); } @@ -1501,7 +1504,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 +1609,12 @@ Reporter.emitDiagnostics(); } + // Check for violations of consumed properties. + if (P.enableConsumedAnalysis) { + consumed::ConsumedAnalyzer Analyzer(S); + 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 @@ -1004,6 +1004,110 @@ Attr.getAttributeSpellingListIndex())); } +static void handleConsumesAttr(Sema &S, Decl *D, + const AttributeList &Attr) { + // Handle functions with the CONSUMES attribute. + + assert(not Attr.isInvalid()); + + if (not checkAttributeNumArgs(S, Attr, 0)) { + return; + } + + if (not 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) { + // Handle functions with the CALLABLE_ALWAYS attribute. + + assert(not Attr.isInvalid()); + + if (not checkAttributeNumArgs(S, Attr, 0)) { + return; + } + + if (not 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) { + // Handle functions with the CALLABLE_WHEN_UNCSONSUMED attribute. + + assert(not Attr.isInvalid()); + + if (not checkAttributeNumArgs(S, Attr, 0)) { + return; + } + + if (not 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) { + // Handle functions with the TESTS_CONSUMED attribute. + + assert(not Attr.isInvalid()); + + if (not checkAttributeNumArgs(S, Attr, 0)) { + return; + } + + if (not 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) { + // Handle functions with the TESTS_UNCONSUMED attribute. + + assert(not Attr.isInvalid()); + + if (not checkAttributeNumArgs(S, Attr, 0)) { + return; + } + + if (not 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) { @@ -5076,6 +5180,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.cpp =================================================================== --- /dev/null +++ test/SemaCXX/warn-consumed-analysis.cpp @@ -0,0 +1,97 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -Wconsumed -std=c++11 %s + +#define CALLABLE_WHEN_UNCONSUMED __attribute__ ((callable_when_unconsumed)) +#define TESTS_UNCONSUMED __attribute__ ((tests_unconsumed)) + +class Bar { + int var; + + public: + Bar(void); + Bar(int val); + Bar(Bar &other); + Bar(Bar &&other); + + Bar& operator=(Bar &other); + Bar& operator=(Bar &&other); + + void operator*(void) CALLABLE_WHEN_UNCONSUMED; + + bool isValid(void) TESTS_UNCONSUMED; +}; + +void testInitializationAndIfStmt(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()) { // \ + \\ expected-warning {{Unnecessary test. Variable 'var0' is known to be in the 'consumed' state}} + *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 = Bar(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()) {// \ + \\ 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 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-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; +};