Index: clang/include/clang/Analysis/Analyses/ReachingDefinitions.h =================================================================== --- /dev/null +++ clang/include/clang/Analysis/Analyses/ReachingDefinitions.h @@ -0,0 +1,268 @@ +//===--- ReachingDefinitions.h ----------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Calculates reaching definitions for a CFG. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_REACHABLE_DEFINITIONS_H +#define LLVM_CLANG_ANALYSIS_ANALYSES_REACHABLE_DEFINITIONS_H + +#include "clang/AST/Decl.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Analysis/AnalysisDeclContext.h" +#include "clang/Analysis/CFG.h" +#include "llvm/ADT/SmallVector.h" +#include + +namespace clang { + +//===----------------------------------------------------------------------===// +// Since reaching definitions was implemented for instructions, it isn't well +// thought out what a definition of a variable is in C/C++, nor what we really +// mean under the term variable. +//===----------------------------------------------------------------------===// + +namespace ReachingDefinitionsDetail { + +//===----------------------------------------------------------------------===// +// What a 'variable' is: +// We define a variable as a VarDecl, and optionally, a non-empty list of +// FieldDecls to express parts of a struct. This implies that we regard the +// fields of a VarDecl as 'variables'. +// TODO: How do we describe the implicit this parameter for methods? +//===----------------------------------------------------------------------===// + +using FieldChainTy = llvm::SmallVector; +bool operator<(const FieldChainTy &Lhs, const FieldChainTy &Rhs); + +struct Variable { + const VarDecl *Var; + FieldChainTy FieldChain; + + Variable(const VarDecl *Var, FieldChainTy FieldChain) + : Var(Var), FieldChain(FieldChain) {} +}; + +struct VariableLess { + // TODO: const ref? but what if we change this to immutablelist? + bool operator()(Variable Lhs, Variable Rhs) { + return std::make_pair(Lhs.Var, Lhs.FieldChain) < + std::make_pair(Rhs.Var, Rhs.FieldChain); + } +}; + +//===----------------------------------------------------------------------===// +// What a 'definition of a variable' is: +// Whatever statement that writes a 'variable', or may write a 'variable'. +// We note whether know that the definition is a write, or just could be a +// write, as well as the CFGElement that contains the defining statement. +//===----------------------------------------------------------------------===// + +class Definition : public Variable { +public: + enum DefinitionKind { Write, Invalidation }; + + CFGBlock::ConstCFGElementRef E; + DefinitionKind Kind; + +public: + Definition(const VarDecl *Var, FieldChainTy FieldChain, + CFGBlock::ConstCFGElementRef E, DefinitionKind Kind) + : Variable(Var, std::move(FieldChain)), E(E), Kind(Kind) {} + + Definition(const VarDecl *Var, CFGBlock::ConstCFGElementRef E, + DefinitionKind Kind) + : Definition(Var, /*FieldChain=*/{}, E, Kind) {} + + const CFGBlock *getCFGBlock() const { return E.getParent(); } + + // (varname [blockid, elementid]) (reason) + void dump() const; +}; + +struct VarAndCFGElementLess { + bool operator()(Definition Lhs, Definition Rhs) const { + return std::tie(Lhs.Var, Lhs.FieldChain, Lhs.E) < + std::tie(Rhs.Var, Rhs.FieldChain, Rhs.E); + } +}; + +/// A set of definitions sorted only by the variable, so that each basic block +/// may only emit a single definition for any single variable. +// TODO: We need to track more then a single definition to a variable for a +// block's GEN set. Say, the static analyzer manages to prove that a potential +// invalidation definition (like a function call) didn't write the variable, we +// need to retrieve the definitions up to that point in the block. +using GenSet = std::set; + +/// A set of definitions sorted by the variable and the location of the +/// definition. For KILL, IN and OUT sets this is correct, because a CFGBlock +/// may kill several definitions of the same variables from different locations. +using DefinitionSet = std::set; + +//===----------------------------------------------------------------------===// +// Determining whether a statement modifies a variable is a challanging, and +// requires using expensive-to-create matchers, and an easily extensible +// interface, hence the use of a builder class outside of the main calculator. +//===----------------------------------------------------------------------===// + +class GenSetBuilder; + +class GenSetMatcherCallback : public ast_matchers::MatchFinder::MatchCallback { +protected: + GenSetBuilder &GSBuilder; + + GenSetMatcherCallback(GenSetBuilder &GSBuilder) : GSBuilder(GSBuilder) {} +}; + +/// Responsible for building the GEN sets for each basic block. +/// +/// Since pointer escapes or function calls in general require us to generate +/// definitions that are invalidations, we need to gather all variables relevant +/// for this analysis, like parameters, locals and globals. We refer to this +/// stage as 'variable finding': +/// * Collect all non-local, visible variables +/// * Collect all local variables within the function that is used +/// +/// The actual building of GEN sets has two stages: +/// 1. For each CFGStmt in the CFGBlock, look for a statement that may be a +/// definition of an expression. ('definition finding') +/// 2. Search expressions for variables recursively. ('expression finding') +class GenSetBuilder { + using DefinitionKind = Definition::DefinitionKind; + + // Fields that shouldn't change after the construction of the builder object. + + llvm::SmallVector, 8> Callbacks; + + ast_matchers::MatchFinder VariableFinder; + ast_matchers::MatchFinder DefinitionFinder; + ast_matchers::MatchFinder ExpressionFinder; + + const Decl *D; + ASTContext *Context = nullptr; + + std::set AllVariables; + std::set AllGlobalVariables; + + // Fields that are changed at and during each GEN set construction. + + GenSet *CurrentGenSet; + Optional CurrentCFGElem; + DefinitionKind CurrentKind = Definition::Write; + +private: + // TODO: Make this public and allow custom matchers to be added? + template + void addMatcher() { + Callbacks.emplace_back(std::make_unique(*this)); + (this->*Finder) + .addMatcher(GenSetMatcherCallbackTy::getMatcher(), + Callbacks.back().get()); + } + +public: + GenSetBuilder(const Decl *D); + + //===--------------------------------------------------------------------===// + // Methods for retrieving a GEN set. + //===--------------------------------------------------------------------===// + void getGenSetForCFGBlock(const CFGBlock *B, GenSet &Gen); + + void getGenSetForParameters(const CFGBlock *Entry, GenSet &Gen); + + //===--------------------------------------------------------------------===// + // Utility methods for building a GEN set. These are public because the + // callback objects will need to call them. + //===--------------------------------------------------------------------===// + + /// When we find a definition to an *expression*, we need to see if that + /// expression is a variable, or some other expression that needs further + /// processing, like in this case: + /// (a, b) = 10; + void handleExpr(const Expr *E, DefinitionKind Kind); + + /// Insert a new defintion of a variable into the current GEN set. + void insertToGenSet(const VarDecl *Var, FieldChainTy FieldChain, + DefinitionKind Kind); + + void insertToGenSet(const VarDecl *Var, DefinitionKind Kind) { + insertToGenSet(Var, FieldChainTy{}, Kind); + } + + void insertToGenSet(const VarDecl *Var, FieldChainTy FieldChain) { + insertToGenSet(Var, FieldChain, CurrentKind); + } + + void insertToGenSet(const VarDecl *Var) { + insertToGenSet(Var, FieldChainTy{}); + } + + void invalidateGlobals() { + for (const Variable &Var : AllGlobalVariables) + insertToGenSet(Var.Var, Var.FieldChain, DefinitionKind::Invalidation); + } + + /// Called during the initialization of the builder to collect all variables + /// in a function. + void addVariable(const VarDecl *Var, FieldChainTy FieldChain); +}; + +} // end of namespace ReachingDefinitionsDetail + +/// Calculates reaching definitions for each basic block. This calculation +/// doesn't try to argue about aliasing, meaning that some definitions are +/// definite write (we know that the variable it written), and some are a result +/// of invalidation, like passing a variable as a non-const reference to a +/// function. +class ReachingDefinitionsCalculator : public ManagedAnalysis { + using GenSetBuilder = ReachingDefinitionsDetail::GenSetBuilder; + +public: + using GenSet = ReachingDefinitionsDetail::GenSet; + using DefinitionKind = ReachingDefinitionsDetail::Definition::DefinitionKind; + using DefinitionSet = ReachingDefinitionsDetail::DefinitionSet; + + void calculate(); + + void dumpKillSet() const; + void dumpGenSet() const; + void dumpReachingDefinitions() const; + + static ReachingDefinitionsCalculator *create(AnalysisDeclContext &Ctx) { + return new ReachingDefinitionsCalculator(Ctx.getDecl(), Ctx.getCFG()); + } + + static const void *getTag() { + static int x; + return &x; + } + +private: + ReachingDefinitionsCalculator(const Decl *D, const CFG *cfg); + + void init(); + + const CFG *cfg; + GenSetBuilder GSBuilder; + + // TODO: Make the GEN set public to allow clients to remove definitions, or + // possibly mark an invalidation as write. + std::map Gen; + std::map Kill; + +public: + std::map In; + std::map Out; +}; + +} // end of namespace clang + +#endif // LLVM_CLANG_ANALYSIS_ANALYSES_REACHABLE_DEFINITIONS_H Index: clang/include/clang/StaticAnalyzer/Checkers/Checkers.td =================================================================== --- clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1415,8 +1415,19 @@ Dependencies<[DebugContainerModeling, IteratorModeling]>, Documentation; -} // end "debug" +def GenSetDumper : Checker<"DumpGenSets">, + HelpText<"Dump the GEN sets for each block in a function">, + Documentation; + +def KillSetDumper : Checker<"DumpKillSets">, + HelpText<"Dump the KILL sets for each block in a function">, + Documentation; +def ReachingDefinitionsDumper : Checker<"DumpReachingDefinitions">, + HelpText<"Dump the reaching definitions for each block in a function">, + Documentation; + +} // end "debug" //===----------------------------------------------------------------------===// // Clone Detection Index: clang/lib/Analysis/CMakeLists.txt =================================================================== --- clang/lib/Analysis/CMakeLists.txt +++ clang/lib/Analysis/CMakeLists.txt @@ -22,6 +22,7 @@ PostOrderCFGView.cpp ProgramPoint.cpp ReachableCode.cpp + ReachingDefinitions.cpp RetainSummaryManager.cpp ThreadSafety.cpp ThreadSafetyCommon.cpp Index: clang/lib/Analysis/ReachingDefinitions.cpp =================================================================== --- /dev/null +++ clang/lib/Analysis/ReachingDefinitions.cpp @@ -0,0 +1,515 @@ +//===--- ReachableDefinitions.cpp -------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Calculates reachable definitions for a variable. +// +//===----------------------------------------------------------------------===// + +#include "clang/Analysis/Analyses/ReachingDefinitions.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclLookups.h" +#include "clang/AST/Expr.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/ASTMatchers/ASTMatchersInternal.h" +#include "clang/Analysis/CFG.h" +#include "clang/Basic/LLVM.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SetOperations.h" +#include "llvm/Support/ErrorHandling.h" +#include + +using namespace clang; +using namespace ReachingDefinitionsDetail; +using namespace ast_matchers; + +using DefinitionKind = Definition::DefinitionKind; + +//===----------------------------------------------------------------------===// +// Utility functions. +//===----------------------------------------------------------------------===// + +/// Print a field chain, e.g. 's.x.y'. Mind that even the indirection operator +/// (->) will be written as a dot, but that shouldn't really be an issue since +/// we don't argue about pointees. +static void dumpFieldChain(const FieldChainTy &FieldChain) { + for (const FieldDecl *Field : FieldChain) + llvm::errs() << "." + Field->getNameAsString(); +} + +#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) +LLVM_DUMP_METHOD static void dumpFieldChainNL(const FieldChainTy &FieldChain) { + dumpFieldChain(FieldChain); + llvm::errs() << '\n'; +} +#endif + +/// The reaching definitions calculator implemented here uses the CFG and some +/// AST analysis and does not employ points-to analysis, so we're ignoring +/// any field that is indirectly contained. +static bool hasIndirection(FieldChainTy FieldChain) { + if (FieldChain.empty()) + return false; + + FieldChainTy::iterator It = llvm::find_if(FieldChain, [](const FieldDecl *F) { + return ento::Loc::isLocType(F->getType()); + }); + + // Yay, no indirection in the chain. + if (It == FieldChain.end()) + return false; + + // If the last element in the chain is a LocType, we're not arguing about + // aliasing, only the value of a pointer object. That is fine. + if (*It == FieldChain.back()) + return false; + + return true; +} + +/// Visit each field of a 'variable', and its subfields recursively, and call +/// the callback on each. +/// +/// \param [in] CB A callback that expects a single FieldChain object. +template +static void forAllFields(const VarDecl *Var, FieldChainTy FieldChain, + CallBack CB) { + if (hasIndirection(FieldChain)) + return; + + const RecordDecl *R = nullptr; + if (FieldChain.empty()) + R = Var->getType()->getAsRecordDecl(); + else + R = FieldChain.back()->getType()->getAsRecordDecl(); + + CB(FieldChain); + + if (R) { + for (const FieldDecl *Field : R->fields()) { + FieldChainTy Cpy = FieldChain; + Cpy.emplace_back(Field); + forAllFields(Var, Cpy, CB); + } + } +} + +static bool killsVar(const Definition &Victim, const GenSet &Set) { + for (const Definition &Perpetrator : Set) + if (std::make_pair(Victim.Var, Victim.FieldChain) == + std::make_pair(Perpetrator.Var, Perpetrator.FieldChain)) + return true; + return false; +} + +static StringRef describeDefinitionKind(DefinitionKind K) { + switch (K) { + case Definition::Write: + return "write"; + case Definition::Invalidation: + return "invalidation"; + } +} + +//===----------------------------------------------------------------------===// +// Methods of Definition. +//===----------------------------------------------------------------------===// + +bool operator<(const FieldChainTy &Lhs, const FieldChainTy &Rhs) { + if (Lhs.size() != Rhs.size()) + return Lhs.size() < Rhs.size(); + + for (size_t Index = 0; Index < Lhs.size(); ++Index) { + if (Lhs[Index] != Rhs[Index]) + return Lhs[Index] < Rhs[Index]; + } + + return false; +} + +void Definition::dump() const { + llvm::errs() << "(" << Var->getNameAsString(); + dumpFieldChain(FieldChain); + + llvm::errs() << ", [" << getCFGBlock()->getIndexInCFG() << ", " + << E.getIndexInBlock() << "])" + << " <" << (describeDefinitionKind(Kind)) << ">"; +} + +//===----------------------------------------------------------------------===// +// Matcher callbacks for constructing GEN sets for the variable finding stage. +//===----------------------------------------------------------------------===// + +class VariableCollectorCB : public GenSetMatcherCallback { +public: + VariableCollectorCB(GenSetBuilder &GSBuilder) + : GenSetMatcherCallback(GSBuilder) {} + + static internal::Matcher getMatcher() { + return stmt(forEachDescendant(declRefExpr().bind("var"))); + } + + virtual void run(const MatchFinder::MatchResult &Result) override { + const auto *E = Result.Nodes.getNodeAs("var"); + assert(E); + if (const VarDecl *Var = dyn_cast(E->getDecl())) + GSBuilder.addVariable(Var, {}); + } +}; + +//===----------------------------------------------------------------------===// +// Matcher callbacks for constructing GEN sets for the definition finding stage. +//===----------------------------------------------------------------------===// + +class AssignmentOperatorCB : public GenSetMatcherCallback { +public: + AssignmentOperatorCB(GenSetBuilder &GSBuilder) + : GenSetMatcherCallback(GSBuilder) {} + + static internal::Matcher getMatcher() { + return binaryOperator(isAssignmentOperator()).bind("assign"); + } + + virtual void run(const MatchFinder::MatchResult &Result) override { + const auto *BO = Result.Nodes.getNodeAs("assign"); + assert(BO); + GSBuilder.handleExpr(BO->getLHS(), DefinitionKind::Write); + } +}; + +class DeclarationCB : public GenSetMatcherCallback { +public: + DeclarationCB(GenSetBuilder &GSBuilder) : GenSetMatcherCallback(GSBuilder) {} + + static internal::Matcher getMatcher() { + return declStmt().bind("decls"); + } + + virtual void run(const MatchFinder::MatchResult &Result) override { + const auto *DS = Result.Nodes.getNodeAs("decls"); + assert(DS); + + for (const Decl *D : DS->decls()) { + const auto *Var = dyn_cast(D); + if (!Var) + continue; + + GSBuilder.insertToGenSet(Var, DefinitionKind::Write); + } + } +}; + +class CallExprCB : public GenSetMatcherCallback { +public: + CallExprCB(GenSetBuilder &GSBuilder) : GenSetMatcherCallback(GSBuilder) {} + + static internal::Matcher getMatcher() { + return callExpr().bind("calls"); + } + + static QualType stripPointers(QualType T) { + while (!T->getPointeeType().isNull()) + T = T->getPointeeType(); + + return T; + } + + static bool isPointerToNonConstTy(QualType T) { + if (T->getPointeeType().isNull()) + return false; + return !stripPointers(T).isConstQualified(); + } + + virtual void run(const MatchFinder::MatchResult &Result) override { + const auto *Call = Result.Nodes.getNodeAs("calls"); + assert(Call); + + GSBuilder.invalidateGlobals(); + + const FunctionDecl *FD = Call->getDirectCallee(); + if (!FD) + return; + bool HasImplicitThisParam = isa(FD); + + for (size_t Index = 0, + E = Call->getNumArgs() - (HasImplicitThisParam ? 1 : 0); + Index < E; ++Index) { + QualType ParamTy = FD->getParamDecl(Index)->getOriginalType(); + if (!ParamTy->isRecordType() && !isPointerToNonConstTy(ParamTy)) + continue; + GSBuilder.handleExpr(Call->getArg(Index + (HasImplicitThisParam ? 1 : 0)), + Definition::Invalidation); + } + } +}; + +//===----------------------------------------------------------------------===// +// Matcher callbacks for constructing GEN sets for the expression finding stage. +//===----------------------------------------------------------------------===// + +class DeclRefExprCB : public GenSetMatcherCallback { +public: + DeclRefExprCB(GenSetBuilder &GSBuilder) : GenSetMatcherCallback(GSBuilder) {} + + static internal::Matcher getMatcher() { + return declRefExpr(to(varDecl().bind("var"))); + } + + virtual void run(const MatchFinder::MatchResult &Result) override { + const auto *Var = Result.Nodes.getNodeAs("var"); + assert(Var); + GSBuilder.insertToGenSet(Var); + } +}; + +class ParenExprCB : public GenSetMatcherCallback { +public: + ParenExprCB(GenSetBuilder &GSBuilder) : GenSetMatcherCallback(GSBuilder) {} + + static internal::Matcher getMatcher() { + return parenExpr().bind("paren"); + } + + virtual void run(const MatchFinder::MatchResult &Result) override { + const auto *Paren = Result.Nodes.getNodeAs("paren"); + assert(Paren); + // TODO + } +}; + +class MemberExprCB : public GenSetMatcherCallback { +public: + MemberExprCB(GenSetBuilder &GSBuilder) : GenSetMatcherCallback(GSBuilder) {} + + static internal::Matcher getMatcher() { + return memberExpr(unless(hasAncestor(memberExpr()))).bind("member"); + } + + virtual void run(const MatchFinder::MatchResult &Result) override { + // If we found an assignemnt to S.x.y.z, the matcher will match for + // the last MemberExpr (z), let's build the fieldchain back up. + const auto *Member = Result.Nodes.getNodeAs("member"); + assert(Member); + FieldChainTy FieldChain; + FieldChain.emplace_back(cast(Member->getMemberDecl())); + + const Expr *NextBase = Member->getBase()->IgnoreParenCasts(); + // Retrieve the next field in the fieldchain. + // FIXME: How about 'a.x.getCookie().x'? + while ((Member = dyn_cast(NextBase))) { + FieldChain.emplace_back(cast(Member->getMemberDecl())); + NextBase = Member->getBase()->IgnoreParenCasts(); + } + std::reverse(FieldChain.begin(), FieldChain.end()); + + GSBuilder.insertToGenSet( + cast(cast(NextBase)->getDecl()), FieldChain); + } +}; + +//===----------------------------------------------------------------------===// +// Methods of GenSetBuilder. +//===----------------------------------------------------------------------===// + +GenSetBuilder::GenSetBuilder(const Decl *D) + : D(D), Context(&D->getASTContext()) { + + // TODO: Should we match the entire TU for nested static variables? + addMatcher<&GenSetBuilder::VariableFinder, VariableCollectorCB>(); + + // TODO: Elvis operator (?:). + addMatcher<&GenSetBuilder::ExpressionFinder, DeclRefExprCB>(); + addMatcher<&GenSetBuilder::ExpressionFinder, MemberExprCB>(); + addMatcher<&GenSetBuilder::ExpressionFinder, ParenExprCB>(); + + // TODO: Destructor calls? Should we be THAT conservative? + // TODO: Moving an object? + // TODO: Method calls? + // TODO: Analyzing a method? + // TODO: What do you do with Objective.*??? + // TODO: Exceptions? + addMatcher<&GenSetBuilder::DefinitionFinder, AssignmentOperatorCB>(); + addMatcher<&GenSetBuilder::DefinitionFinder, DeclarationCB>(); + addMatcher<&GenSetBuilder::DefinitionFinder, CallExprCB>(); + + // Collect all used variables. + if (const auto *FD = dyn_cast(D)) { + VariableFinder.match(*FD, FD->getASTContext()); + for (const ParmVarDecl *Param : FD->parameters()) + addVariable(Param, {}); + } + + // Collect all visible, non-local variables in a separate set. + // TODO: Does this actually collect all of them? + const DeclContext *DC = D->getDeclContext(); + while (DC) { + DC = DC->getPrimaryContext(); + for (const Decl *Res : DC->decls()) + if (const auto *VD = dyn_cast(Res)) + forAllFields(VD, FieldChainTy{}, [this, VD](const FieldChainTy &FC) { + AllGlobalVariables.emplace(VD, FC); + }); + DC = DC->getParent(); + } +} + +void GenSetBuilder::getGenSetForCFGBlock(const CFGBlock *B, GenSet &Gen) { + if (B->empty()) + return; + + VariableFinder.match(*D, D->getASTContext()); + + CurrentGenSet = &Gen; + + for (CFGBlock::ConstCFGElementRef E : B->rrefs()) { + if (E->getKind() != CFGElement::Kind::Statement) + continue; + CurrentCFGElem = E; + + const Stmt *S = E->castAs().getStmt(); + assert(S); + DefinitionFinder.match(*S, D->getASTContext()); + } + + CurrentGenSet = nullptr; +} + +void GenSetBuilder::getGenSetForParameters(const CFGBlock *Entry, GenSet &Gen) { + CurrentGenSet = &Gen; + CurrentCFGElem = {Entry, 0}; + if (const auto *F = dyn_cast(D)) { + for (const ParmVarDecl *Param : F->parameters()) { + insertToGenSet(Param, Definition::DefinitionKind::Write); + } + } + CurrentGenSet = nullptr; +} + +void GenSetBuilder::handleExpr(const Expr *E, DefinitionKind Kind) { + CurrentKind = Kind; + ExpressionFinder.match(*E, *Context); +} + +void GenSetBuilder::insertToGenSet(const VarDecl *Var, FieldChainTy FieldChain, + DefinitionKind Kind) { + forAllFields(Var, FieldChain, [this, Var, Kind](const FieldChainTy &FC) { + CurrentGenSet->emplace(Var, FC, *CurrentCFGElem, Kind); + // TODO: Invalidate all variables that can alias with this type. + }); +} + +void GenSetBuilder::addVariable(const VarDecl *Var, FieldChainTy FieldChain) { + forAllFields(Var, FieldChain, [this, Var](const FieldChainTy &FC) { + AllVariables.emplace(Var, FC); + }); +} + +//===----------------------------------------------------------------------===// +// Methods of ReachingDefinitionsCalculator. +//===----------------------------------------------------------------------===// + +ReachingDefinitionsCalculator::ReachingDefinitionsCalculator(const Decl *D, + const CFG *cfg) + : cfg(cfg), GSBuilder(D) { + + for (const CFGBlock *B : *cfg) + GSBuilder.getGenSetForCFGBlock(B, Gen[B]); + GSBuilder.getGenSetForParameters(&cfg->getEntry(), Gen[&cfg->getEntry()]); + + calculate(); +} + +void ReachingDefinitionsCalculator::init() { + llvm::SmallVector AllDefinitions; + for (const std::pair G : Gen) + for (const Definition &Def : G.second) + AllDefinitions.push_back(Def); + + for (const std::pair G : Gen) + for (const Definition &Def : AllDefinitions) + if (G.first != Def.getCFGBlock() && killsVar(Def, G.second)) + Kill[G.first].insert(Def); +} + +using WorklistTy = llvm::SmallVector; + +void ReachingDefinitionsCalculator::calculate() { + init(); + + for (const std::pair G : Gen) + Out[G.first] = {G.second.begin(), G.second.end()}; + + WorklistTy Worklist({cfg->begin(), cfg->end()}); + + while (!Worklist.empty()) { + const CFGBlock *N = Worklist.pop_back_val(); + + for (const CFGBlock *Pred : N->preds()) + llvm::set_union(In[N], Out[Pred]); + + bool HasChanged = + llvm::set_union(Out[N], llvm::set_difference(In[N], Kill[N])); + + if (llvm::set_union(Out[N], Gen[N])) + HasChanged = true; + + if (HasChanged) { + for (const CFGBlock *Succ : N->succs()) + Worklist.push_back(Succ); + } + } +} + +void ReachingDefinitionsCalculator::dumpGenSet() const { + llvm::errs() << "GEN sets: blockid (varname [blockid, elementid])\n"; + for (const std::pair D : Gen) { + size_t BlockID = llvm::find(*cfg, D.first) - cfg->begin(); + for (const Definition &Def : D.second) { + llvm::errs() << BlockID << ' '; + Def.dump(); + llvm::errs() << '\n'; + } + } +} + +void ReachingDefinitionsCalculator::dumpKillSet() const { + llvm::errs() << "KILL sets: blockid (varname [blockid, elementid])\n"; + for (const std::pair D : Kill) { + size_t BlockID = llvm::find(*cfg, D.first) - cfg->begin(); + for (const Definition &Def : D.second) { + llvm::errs() << BlockID << ' '; + Def.dump(); + llvm::errs() << '\n'; + } + } +} + +void ReachingDefinitionsCalculator::dumpReachingDefinitions() const { + llvm::errs() << "Reaching definition sets: " + "blockid IN/OUT (varname [blockid, elementid])\n"; + for (const CFGBlock *B : *cfg) { + size_t BlockID = llvm::find(*cfg, B) - cfg->begin(); + if (In.count(B)) { + for (const Definition &Def : In.find(B)->second) { + llvm::errs() << BlockID << " IN "; + Def.dump(); + llvm::errs() << '\n'; + } + } + + if (Out.count(B)) { + for (const Definition &Def : Out.find(B)->second) { + llvm::errs() << BlockID << " OUT "; + Def.dump(); + llvm::errs() << '\n'; + } + } + } +} Index: clang/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp =================================================================== --- clang/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp +++ clang/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp @@ -10,12 +10,13 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/Analysis/Analyses/Dominators.h" #include "clang/Analysis/Analyses/LiveVariables.h" +#include "clang/Analysis/Analyses/ReachingDefinitions.h" #include "clang/Analysis/CallGraph.h" -#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" @@ -102,6 +103,72 @@ return true; } +//===----------------------------------------------------------------------===// +// GenSetDumper +//===----------------------------------------------------------------------===// + +namespace { +class GenSetDumper : public Checker { +public: + void checkASTCodeBody(const Decl *D, AnalysisManager &mgr, + BugReporter &BR) const { + if (AnalysisDeclContext *AC = mgr.getAnalysisDeclContext(D)) + AC->getAnalysis()->dumpGenSet(); + } +}; +} // namespace + +void ento::registerGenSetDumper(CheckerManager &mgr) { + mgr.registerChecker(); +} + +bool ento::shouldRegisterGenSetDumper(const LangOptions &LO) { return true; } + +//===----------------------------------------------------------------------===// +// KillSetDumper +//===----------------------------------------------------------------------===// + +namespace { +class KillSetDumper : public Checker { +public: + void checkASTCodeBody(const Decl *D, AnalysisManager &mgr, + BugReporter &BR) const { + if (AnalysisDeclContext *AC = mgr.getAnalysisDeclContext(D)) + AC->getAnalysis()->dumpKillSet(); + } +}; +} // namespace + +void ento::registerKillSetDumper(CheckerManager &mgr) { + mgr.registerChecker(); +} + +bool ento::shouldRegisterKillSetDumper(const LangOptions &LO) { return true; } + +//===----------------------------------------------------------------------===// +// ReachingDefinitionsDumper +//===----------------------------------------------------------------------===// + +namespace { +class ReachingDefinitionsDumper : public Checker { +public: + void checkASTCodeBody(const Decl *D, AnalysisManager &mgr, + BugReporter &BR) const { + if (AnalysisDeclContext *AC = mgr.getAnalysisDeclContext(D)) + AC->getAnalysis() + ->dumpReachingDefinitions(); + } +}; +} // namespace + +void ento::registerReachingDefinitionsDumper(CheckerManager &mgr) { + mgr.registerChecker(); +} + +bool ento::shouldRegisterReachingDefinitionsDumper(const LangOptions &LO) { + return true; +} + //===----------------------------------------------------------------------===// // LiveVariablesDumper //===----------------------------------------------------------------------===// Index: clang/test/Analysis/dump-definitions-local-invalidation.cpp =================================================================== --- /dev/null +++ clang/test/Analysis/dump-definitions-local-invalidation.cpp @@ -0,0 +1,100 @@ +// RUN: %clang_analyze_cc1 %s \ +// RUN: -analyzer-checker=debug.DumpCFG \ +// RUN: -analyzer-checker=debug.DumpGenSets \ +// RUN: -analyzer-checker=debug.DumpKillSets \ +// RUN: -analyzer-checker=debug.DumpReachingDefinitions \ +// RUN: 2>&1 | FileCheck %s + +int global_var; + +namespace simple_invalidation { + +bool coin(); + +void invalidate(int &i, double d); + +void invalidateOnFnCall() { + int i; + double d = 0.0; + + if (coin()) + i = 5; + + if (coin()) + d = 2.3; + + invalidate(i, d); +} +// -> [B4] -> -> [B2] -> +// / \ / \ +// [B6 (ENTRY)] -> [B5] ------> [B3] ------> [B1] -> [B0 (EXIT)] + +// CHECK: GEN sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 1 (global_var, [1, 5]) +// CHECK-NEXT: 1 (i, [1, 5]) +// CHECK-NEXT: 2 (d, [2, 2]) +// CHECK-NEXT: 3 (global_var, [3, 2]) +// CHECK-NEXT: 4 (i, [4, 2]) +// CHECK-NEXT: 5 (global_var, [5, 5]) +// CHECK-NEXT: 5 (i, [5, 0]) +// CHECK-NEXT: 5 (d, [5, 2]) +// CHECK-NEXT: KILL sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 1 (global_var, [3, 2]) +// CHECK-NEXT: 1 (global_var, [5, 5]) +// CHECK-NEXT: 1 (i, [4, 2]) +// CHECK-NEXT: 1 (i, [5, 0]) +// CHECK-NEXT: 2 (d, [5, 2]) +// CHECK-NEXT: 3 (global_var, [1, 5]) +// CHECK-NEXT: 3 (global_var, [5, 5]) +// CHECK-NEXT: 4 (i, [1, 5]) +// CHECK-NEXT: 4 (i, [5, 0]) +// CHECK-NEXT: 5 (global_var, [1, 5]) +// CHECK-NEXT: 5 (global_var, [3, 2]) +// CHECK-NEXT: 5 (i, [1, 5]) +// CHECK-NEXT: 5 (i, [4, 2]) +// CHECK-NEXT: 5 (d, [2, 2]) +// CHECK-NEXT: Reaching definition sets: blockid IN/OUT (varname [blockid, elementid]) +// CHECK-NEXT: 0 IN (global_var, [1, 5]) +// CHECK-NEXT: 0 IN (i, [1, 5]) +// CHECK-NEXT: 0 IN (d, [2, 2]) +// CHECK-NEXT: 0 IN (d, [5, 2]) +// CHECK-NEXT: 0 OUT (global_var, [1, 5]) +// CHECK-NEXT: 0 OUT (i, [1, 5]) +// CHECK-NEXT: 0 OUT (d, [2, 2]) +// CHECK-NEXT: 0 OUT (d, [5, 2]) +// CHECK-NEXT: 1 IN (global_var, [3, 2]) +// CHECK-NEXT: 1 IN (i, [4, 2]) +// CHECK-NEXT: 1 IN (i, [5, 0]) +// CHECK-NEXT: 1 IN (d, [2, 2]) +// CHECK-NEXT: 1 IN (d, [5, 2]) +// CHECK-NEXT: 1 OUT (global_var, [1, 5]) +// CHECK-NEXT: 1 OUT (i, [1, 5]) +// CHECK-NEXT: 1 OUT (d, [2, 2]) +// CHECK-NEXT: 1 OUT (d, [5, 2]) +// CHECK-NEXT: 2 IN (global_var, [3, 2]) +// CHECK-NEXT: 2 IN (i, [4, 2]) +// CHECK-NEXT: 2 IN (i, [5, 0]) +// CHECK-NEXT: 2 IN (d, [5, 2]) +// CHECK-NEXT: 2 OUT (global_var, [3, 2]) +// CHECK-NEXT: 2 OUT (i, [4, 2]) +// CHECK-NEXT: 2 OUT (i, [5, 0]) +// CHECK-NEXT: 2 OUT (d, [2, 2]) +// CHECK-NEXT: 3 IN (global_var, [5, 5]) +// CHECK-NEXT: 3 IN (i, [4, 2]) +// CHECK-NEXT: 3 IN (i, [5, 0]) +// CHECK-NEXT: 3 IN (d, [5, 2]) +// CHECK-NEXT: 3 OUT (global_var, [3, 2]) +// CHECK-NEXT: 3 OUT (i, [4, 2]) +// CHECK-NEXT: 3 OUT (i, [5, 0]) +// CHECK-NEXT: 3 OUT (d, [5, 2]) +// CHECK-NEXT: 4 IN (global_var, [5, 5]) +// CHECK-NEXT: 4 IN (i, [5, 0]) +// CHECK-NEXT: 4 IN (d, [5, 2]) +// CHECK-NEXT: 4 OUT (global_var, [5, 5]) +// CHECK-NEXT: 4 OUT (i, [4, 2]) +// CHECK-NEXT: 4 OUT (d, [5, 2]) +// CHECK-NEXT: 5 OUT (global_var, [5, 5]) +// CHECK-NEXT: 5 OUT (i, [5, 0]) +// CHECK-NEXT: 5 OUT (d, [5, 2]) + +} // namespace simple_invalidation Index: clang/test/Analysis/dump-definitions-nontrivial-expressions.cpp =================================================================== --- /dev/null +++ clang/test/Analysis/dump-definitions-nontrivial-expressions.cpp @@ -0,0 +1,94 @@ +// RUN: %clang_analyze_cc1 %s \ +// RUN: -analyzer-checker=debug.DumpCFG \ +// RUN: -analyzer-checker=debug.DumpGenSets \ +// RUN: -analyzer-checker=debug.DumpKillSets \ +// RUN: -analyzer-checker=debug.DumpReachingDefinitions \ +// RUN: 2>&1 | FileCheck %s + +void lhs_in_parantheses(int a, int b) { + (a, b) = 5; +} +// CHECK: GEN sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 2 (a, [2, 0]) +// CHECK-NEXT: 2 (b, [2, 0]) +// CHECK-NEXT: KILL sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: Reaching definition sets: blockid IN/OUT (varname [blockid, elementid]) +// CHECK-NEXT: 0 IN (a, [2, 0]) +// CHECK-NEXT: 0 IN (b, [2, 0]) +// CHECK-NEXT: 0 OUT (a, [2, 0]) +// CHECK-NEXT: 0 OUT (b, [2, 0]) +// CHECK-NEXT: 1 IN (a, [2, 0]) +// CHECK-NEXT: 1 IN (b, [2, 0]) +// CHECK-NEXT: 1 OUT (a, [2, 0]) +// CHECK-NEXT: 1 OUT (b, [2, 0]) +// CHECK-NEXT: 2 OUT (a, [2, 0]) +// CHECK-NEXT: 2 OUT (b, [2, 0]) + +void lhs_buried_in_parantheses(int a, int b) { + ((((((((a, b)))))))) = 5; +} +// CHECK: GEN sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 2 (a, [2, 0]) +// CHECK-NEXT: 2 (b, [2, 0]) +// CHECK-NEXT: KILL sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: Reaching definition sets: blockid IN/OUT (varname [blockid, elementid]) +// CHECK-NEXT: 0 IN (a, [2, 0]) +// CHECK-NEXT: 0 IN (b, [2, 0]) +// CHECK-NEXT: 0 OUT (a, [2, 0]) +// CHECK-NEXT: 0 OUT (b, [2, 0]) +// CHECK-NEXT: 1 IN (a, [2, 0]) +// CHECK-NEXT: 1 IN (b, [2, 0]) +// CHECK-NEXT: 1 OUT (a, [2, 0]) +// CHECK-NEXT: 1 OUT (b, [2, 0]) +// CHECK-NEXT: 2 OUT (a, [2, 0]) +// CHECK-NEXT: 2 OUT (b, [2, 0]) + +void lhs_buried_in_parantheses2(int a, int b) { + ((((((((a))))), b))) = 5; +} +// CHECK: GEN sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 2 (a, [2, 0]) +// CHECK-NEXT: 2 (b, [2, 0]) +// CHECK-NEXT: KILL sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: Reaching definition sets: blockid IN/OUT (varname [blockid, elementid]) +// CHECK-NEXT: 0 IN (a, [2, 0]) +// CHECK-NEXT: 0 IN (b, [2, 0]) +// CHECK-NEXT: 0 OUT (a, [2, 0]) +// CHECK-NEXT: 0 OUT (b, [2, 0]) +// CHECK-NEXT: 1 IN (a, [2, 0]) +// CHECK-NEXT: 1 IN (b, [2, 0]) +// CHECK-NEXT: 1 OUT (a, [2, 0]) +// CHECK-NEXT: 1 OUT (b, [2, 0]) +// CHECK-NEXT: 2 OUT (a, [2, 0]) +// CHECK-NEXT: 2 OUT (b, [2, 0]) + +void lhs_is_conditional_operator(int a, int b) { + (a ? a : b) = 5; +} +// CHECK: GEN sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 5 (a, [5, 0]) +// CHECK-NEXT: 5 (b, [5, 0]) +// CHECK-NEXT: KILL sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: Reaching definition sets: blockid IN/OUT (varname [blockid, elementid]) +// CHECK-NEXT: 0 IN (a, [5, 0]) +// CHECK-NEXT: 0 IN (b, [5, 0]) +// CHECK-NEXT: 0 OUT (a, [5, 0]) +// CHECK-NEXT: 0 OUT (b, [5, 0]) +// CHECK-NEXT: 1 IN (a, [5, 0]) +// CHECK-NEXT: 1 IN (b, [5, 0]) +// CHECK-NEXT: 1 OUT (a, [5, 0]) +// CHECK-NEXT: 1 OUT (b, [5, 0]) +// CHECK-NEXT: 2 IN (a, [5, 0]) +// CHECK-NEXT: 2 IN (b, [5, 0]) +// CHECK-NEXT: 2 OUT (a, [5, 0]) +// CHECK-NEXT: 2 OUT (b, [5, 0]) +// CHECK-NEXT: 3 IN (a, [5, 0]) +// CHECK-NEXT: 3 IN (b, [5, 0]) +// CHECK-NEXT: 3 OUT (a, [5, 0]) +// CHECK-NEXT: 3 OUT (b, [5, 0]) +// CHECK-NEXT: 4 IN (a, [5, 0]) +// CHECK-NEXT: 4 IN (b, [5, 0]) +// CHECK-NEXT: 4 OUT (a, [5, 0]) +// CHECK-NEXT: 4 OUT (b, [5, 0]) +// CHECK-NEXT: 5 OUT (a, [5, 0]) +// CHECK-NEXT: 5 OUT (b, [5, 0]) Index: clang/test/Analysis/dump-definitions-records.cpp =================================================================== --- /dev/null +++ clang/test/Analysis/dump-definitions-records.cpp @@ -0,0 +1,252 @@ +// RUN: %clang_analyze_cc1 %s \ +// RUN: -analyzer-checker=debug.DumpCFG \ +// RUN: -analyzer-checker=debug.DumpGenSets \ +// RUN: -analyzer-checker=debug.DumpKillSets \ +// RUN: -analyzer-checker=debug.DumpReachingDefinitions \ +// RUN: 2>&1 | FileCheck %s + +int global_var; + +namespace struct1 { + +struct S { + int a, b; +}; + +void structtest() { + S s; + s.a = 5; +} + +// CHECK: GEN sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 1 (s, [1, 1]) +// CHECK-NEXT: 1 (s.a, [1, 5]) +// CHECK-NEXT: 1 (s.b, [1, 1]) +// CHECK-NEXT: KILL sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: Reaching definition sets: blockid IN/OUT (varname [blockid, elementid]) +// CHECK-NEXT: 0 IN (s, [1, 1]) +// CHECK-NEXT: 0 IN (s.a, [1, 5]) +// CHECK-NEXT: 0 IN (s.b, [1, 1]) +// CHECK-NEXT: 0 OUT (s, [1, 1]) +// CHECK-NEXT: 0 OUT (s.a, [1, 5]) +// CHECK-NEXT: 0 OUT (s.b, [1, 1]) +// CHECK-NEXT: 1 OUT (s, [1, 1]) +// CHECK-NEXT: 1 OUT (s.a, [1, 5]) +// CHECK-NEXT: 1 OUT (s.b, [1, 1]) + +void struct_param(S s) { + s.a = 5; +} + +// CHECK: GEN sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 1 (s.a, [1, 3]) +// CHECK-NEXT: 2 (s, [2, 0]) +// CHECK-NEXT: 2 (s.a, [2, 0]) +// CHECK-NEXT: 2 (s.b, [2, 0]) +// CHECK-NEXT: KILL sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 1 (s.a, [2, 0]) +// CHECK-NEXT: 2 (s.a, [1, 3]) +// CHECK-NEXT: Reaching definition sets: blockid IN/OUT (varname [blockid, elementid]) +// CHECK-NEXT: 0 IN (s, [2, 0]) +// CHECK-NEXT: 0 IN (s.a, [1, 3]) +// CHECK-NEXT: 0 IN (s.b, [2, 0]) +// CHECK-NEXT: 0 OUT (s, [2, 0]) +// CHECK-NEXT: 0 OUT (s.a, [1, 3]) +// CHECK-NEXT: 0 OUT (s.b, [2, 0]) +// CHECK-NEXT: 1 IN (s, [2, 0]) +// CHECK-NEXT: 1 IN (s.a, [2, 0]) +// CHECK-NEXT: 1 IN (s.b, [2, 0]) +// CHECK-NEXT: 1 OUT (s, [2, 0]) +// CHECK-NEXT: 1 OUT (s.a, [1, 3]) +// CHECK-NEXT: 1 OUT (s.b, [2, 0]) +// CHECK-NEXT: 2 OUT (s, [2, 0]) +// CHECK-NEXT: 2 OUT (s.a, [2, 0]) +// CHECK-NEXT: 2 OUT (s.b, [2, 0]) + +} // end of namespace struct1 + +namespace struct2 { + +struct Inner { + int a, b; +}; + +struct S { + Inner x; + Inner y; +}; + +void struct_param(S s) { + s.x.b = 7; + s.y.b = 5; +} + +// CHECK: GEN sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 1 (s.x.b, [1, 4]) +// CHECK-NEXT: 1 (s.y.b, [1, 9]) +// CHECK-NEXT: 2 (s, [2, 0]) +// CHECK-NEXT: 2 (s.x, [2, 0]) +// CHECK-NEXT: 2 (s.x.a, [2, 0]) +// CHECK-NEXT: 2 (s.x.b, [2, 0]) +// CHECK-NEXT: 2 (s.y, [2, 0]) +// CHECK-NEXT: 2 (s.y.a, [2, 0]) +// CHECK-NEXT: 2 (s.y.b, [2, 0]) +// CHECK-NEXT: KILL sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 1 (s.x.b, [2, 0]) +// CHECK-NEXT: 1 (s.y.b, [2, 0]) +// CHECK-NEXT: 2 (s.x.b, [1, 4]) +// CHECK-NEXT: 2 (s.y.b, [1, 9]) +// CHECK-NEXT: Reaching definition sets: blockid IN/OUT (varname [blockid, elementid]) +// CHECK-NEXT: 0 IN (s, [2, 0]) +// CHECK-NEXT: 0 IN (s.x, [2, 0]) +// CHECK-NEXT: 0 IN (s.x.a, [2, 0]) +// CHECK-NEXT: 0 IN (s.x.b, [1, 4]) +// CHECK-NEXT: 0 IN (s.y, [2, 0]) +// CHECK-NEXT: 0 IN (s.y.a, [2, 0]) +// CHECK-NEXT: 0 IN (s.y.b, [1, 9]) +// CHECK-NEXT: 0 OUT (s, [2, 0]) +// CHECK-NEXT: 0 OUT (s.x, [2, 0]) +// CHECK-NEXT: 0 OUT (s.x.a, [2, 0]) +// CHECK-NEXT: 0 OUT (s.x.b, [1, 4]) +// CHECK-NEXT: 0 OUT (s.y, [2, 0]) +// CHECK-NEXT: 0 OUT (s.y.a, [2, 0]) +// CHECK-NEXT: 0 OUT (s.y.b, [1, 9]) +// CHECK-NEXT: 1 IN (s, [2, 0]) +// CHECK-NEXT: 1 IN (s.x, [2, 0]) +// CHECK-NEXT: 1 IN (s.x.a, [2, 0]) +// CHECK-NEXT: 1 IN (s.x.b, [2, 0]) +// CHECK-NEXT: 1 IN (s.y, [2, 0]) +// CHECK-NEXT: 1 IN (s.y.a, [2, 0]) +// CHECK-NEXT: 1 IN (s.y.b, [2, 0]) +// CHECK-NEXT: 1 OUT (s, [2, 0]) +// CHECK-NEXT: 1 OUT (s.x, [2, 0]) +// CHECK-NEXT: 1 OUT (s.x.a, [2, 0]) +// CHECK-NEXT: 1 OUT (s.x.b, [1, 4]) +// CHECK-NEXT: 1 OUT (s.y, [2, 0]) +// CHECK-NEXT: 1 OUT (s.y.a, [2, 0]) +// CHECK-NEXT: 1 OUT (s.y.b, [1, 9]) +// CHECK-NEXT: 2 OUT (s, [2, 0]) +// CHECK-NEXT: 2 OUT (s.x, [2, 0]) +// CHECK-NEXT: 2 OUT (s.x.a, [2, 0]) +// CHECK-NEXT: 2 OUT (s.x.b, [2, 0]) +// CHECK-NEXT: 2 OUT (s.y, [2, 0]) +// CHECK-NEXT: 2 OUT (s.y.a, [2, 0]) +// CHECK-NEXT: 2 OUT (s.y.b, [2, 0]) + +} // namespace struct2 + +namespace struct_ptr { + +struct Inner { + int a, b; +}; + +struct S { + Inner *x; + Inner &y; + Inner z; + S(); +}; + +void struct_param(S s) { + s.x->b = 7; + s.y.b = 5; + s.z.a = 5; +} + +// CHECK: GEN sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 1 (s.z.a, [1, 15]) +// CHECK-NEXT: 2 (s, [2, 0]) +// CHECK-NEXT: 2 (s.x, [2, 0]) +// CHECK-NEXT: 2 (s.y, [2, 0]) +// CHECK-NEXT: 2 (s.z, [2, 0]) +// CHECK-NEXT: 2 (s.z.a, [2, 0]) +// CHECK-NEXT: 2 (s.z.b, [2, 0]) +// CHECK-NEXT: KILL sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 1 (s.z.a, [2, 0]) +// CHECK-NEXT: 2 (s.z.a, [1, 15]) +// CHECK-NEXT: Reaching definition sets: blockid IN/OUT (varname [blockid, elementid]) +// CHECK-NEXT: 0 IN (s, [2, 0]) +// CHECK-NEXT: 0 IN (s.x, [2, 0]) +// CHECK-NEXT: 0 IN (s.y, [2, 0]) +// CHECK-NEXT: 0 IN (s.z, [2, 0]) +// CHECK-NEXT: 0 IN (s.z.a, [1, 15]) +// CHECK-NEXT: 0 IN (s.z.b, [2, 0]) +// CHECK-NEXT: 0 OUT (s, [2, 0]) +// CHECK-NEXT: 0 OUT (s.x, [2, 0]) +// CHECK-NEXT: 0 OUT (s.y, [2, 0]) +// CHECK-NEXT: 0 OUT (s.z, [2, 0]) +// CHECK-NEXT: 0 OUT (s.z.a, [1, 15]) +// CHECK-NEXT: 0 OUT (s.z.b, [2, 0]) +// CHECK-NEXT: 1 IN (s, [2, 0]) +// CHECK-NEXT: 1 IN (s.x, [2, 0]) +// CHECK-NEXT: 1 IN (s.y, [2, 0]) +// CHECK-NEXT: 1 IN (s.z, [2, 0]) +// CHECK-NEXT: 1 IN (s.z.a, [2, 0]) +// CHECK-NEXT: 1 IN (s.z.b, [2, 0]) +// CHECK-NEXT: 1 OUT (s, [2, 0]) +// CHECK-NEXT: 1 OUT (s.x, [2, 0]) +// CHECK-NEXT: 1 OUT (s.y, [2, 0]) +// CHECK-NEXT: 1 OUT (s.z, [2, 0]) +// CHECK-NEXT: 1 OUT (s.z.a, [1, 15]) +// CHECK-NEXT: 1 OUT (s.z.b, [2, 0]) +// CHECK-NEXT: 2 OUT (s, [2, 0]) +// CHECK-NEXT: 2 OUT (s.x, [2, 0]) +// CHECK-NEXT: 2 OUT (s.y, [2, 0]) +// CHECK-NEXT: 2 OUT (s.z, [2, 0]) +// CHECK-NEXT: 2 OUT (s.z.a, [2, 0]) +// CHECK-NEXT: 2 OUT (s.z.b, [2, 0]) + +void struct_switten(S s) { + s.x->b = 7; + s.y = {}; + s.z.a = 5; +} +// CHECK: GEN sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 1 (global_var, [1, 12]) +// CHECK-NEXT: 1 (s.z.a, [1, 17]) +// CHECK-NEXT: 2 (s, [2, 0]) +// CHECK-NEXT: 2 (s.x, [2, 0]) +// CHECK-NEXT: 2 (s.y, [2, 0]) +// CHECK-NEXT: 2 (s.z, [2, 0]) +// CHECK-NEXT: 2 (s.z.a, [2, 0]) +// CHECK-NEXT: 2 (s.z.b, [2, 0]) +// CHECK-NEXT: KILL sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 1 (s.z.a, [2, 0]) +// CHECK-NEXT: 2 (s.z.a, [1, 17]) +// CHECK-NEXT: Reaching definition sets: blockid IN/OUT (varname [blockid, elementid]) +// CHECK-NEXT: 0 IN (global_var, [1, 12]) +// CHECK-NEXT: 0 IN (s, [2, 0]) +// CHECK-NEXT: 0 IN (s.x, [2, 0]) +// CHECK-NEXT: 0 IN (s.y, [2, 0]) +// CHECK-NEXT: 0 IN (s.z, [2, 0]) +// CHECK-NEXT: 0 IN (s.z.a, [1, 17]) +// CHECK-NEXT: 0 IN (s.z.b, [2, 0]) +// CHECK-NEXT: 0 OUT (global_var, [1, 12]) +// CHECK-NEXT: 0 OUT (s, [2, 0]) +// CHECK-NEXT: 0 OUT (s.x, [2, 0]) +// CHECK-NEXT: 0 OUT (s.y, [2, 0]) +// CHECK-NEXT: 0 OUT (s.z, [2, 0]) +// CHECK-NEXT: 0 OUT (s.z.a, [1, 17]) +// CHECK-NEXT: 0 OUT (s.z.b, [2, 0]) +// CHECK-NEXT: 1 IN (s, [2, 0]) +// CHECK-NEXT: 1 IN (s.x, [2, 0]) +// CHECK-NEXT: 1 IN (s.y, [2, 0]) +// CHECK-NEXT: 1 IN (s.z, [2, 0]) +// CHECK-NEXT: 1 IN (s.z.a, [2, 0]) +// CHECK-NEXT: 1 IN (s.z.b, [2, 0]) +// CHECK-NEXT: 1 OUT (global_var, [1, 12]) +// CHECK-NEXT: 1 OUT (s, [2, 0]) +// CHECK-NEXT: 1 OUT (s.x, [2, 0]) +// CHECK-NEXT: 1 OUT (s.y, [2, 0]) +// CHECK-NEXT: 1 OUT (s.z, [2, 0]) +// CHECK-NEXT: 1 OUT (s.z.a, [1, 17]) +// CHECK-NEXT: 1 OUT (s.z.b, [2, 0]) +// CHECK-NEXT: 2 OUT (s, [2, 0]) +// CHECK-NEXT: 2 OUT (s.x, [2, 0]) +// CHECK-NEXT: 2 OUT (s.y, [2, 0]) +// CHECK-NEXT: 2 OUT (s.z, [2, 0]) +// CHECK-NEXT: 2 OUT (s.z.a, [2, 0]) +// CHECK-NEXT: 2 OUT (s.z.b, [2, 0]) + +} // namespace struct_ptr Index: clang/test/Analysis/dump-definitions.cpp =================================================================== --- /dev/null +++ clang/test/Analysis/dump-definitions.cpp @@ -0,0 +1,402 @@ +// RUN: %clang_analyze_cc1 %s \ +// RUN: -analyzer-checker=debug.DumpCFG \ +// RUN: -analyzer-checker=debug.DumpGenSets \ +// RUN: -analyzer-checker=debug.DumpKillSets \ +// RUN: -analyzer-checker=debug.DumpReachingDefinitions \ +// RUN: 2>&1 | FileCheck %s + +int global_var; + +int getInt(); +int *getIntPtr(); +bool coin(); + +void single_vardecl_in_declstmt() { + int *ptr = getIntPtr(); +} +// [B2 (ENTRY)] -> [B1] -> [B0 (EXIT)] + +// CHECK: GEN sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 1 (global_var, [1, 2]) +// CHECK-NEXT: 1 (ptr, [1, 3]) +// CHECK-NEXT: KILL sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: Reaching definition sets: blockid IN/OUT (varname [blockid, elementid]) +// CHECK-NEXT: 0 IN (global_var, [1, 2]) +// CHECK-NEXT: 0 IN (ptr, [1, 3]) +// CHECK-NEXT: 0 OUT (global_var, [1, 2]) +// CHECK-NEXT: 0 OUT (ptr, [1, 3]) +// CHECK-NEXT: 1 OUT (global_var, [1, 2]) +// CHECK-NEXT: 1 OUT (ptr, [1, 3]) + +void multiple_vardecl_in_declstmt() { + int *ptr = getIntPtr(), i; +} +// [B2 (ENTRY)] -> [B1] -> [B0 (EXIT)] + +// CHECK: GEN sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 1 (global_var, [1, 2]) +// CHECK-NEXT: 1 (ptr, [1, 3]) +// CHECK-NEXT: 1 (i, [1, 4]) +// CHECK-NEXT: KILL sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: Reaching definition sets: blockid IN/OUT (varname [blockid, elementid]) +// CHECK-NEXT: 0 IN (global_var, [1, 2]) +// CHECK-NEXT: 0 IN (ptr, [1, 3]) +// CHECK-NEXT: 0 IN (i, [1, 4]) +// CHECK-NEXT: 0 OUT (global_var, [1, 2]) +// CHECK-NEXT: 0 OUT (ptr, [1, 3]) +// CHECK-NEXT: 0 OUT (i, [1, 4]) +// CHECK-NEXT: 1 OUT (global_var, [1, 2]) +// CHECK-NEXT: 1 OUT (ptr, [1, 3]) +// CHECK-NEXT: 1 OUT (i, [1, 4]) + +void function_and_vardecl_in_declstmt() { + int *ptr = getIntPtr(), a(); +} +// [B2 (ENTRY)] -> [B1] -> [B0 (EXIT)] + +// CHECK: GEN sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 1 (global_var, [1, 2]) +// CHECK-NEXT: 1 (ptr, [1, 3]) +// CHECK-NEXT: KILL sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: Reaching definition sets: blockid IN/OUT (varname [blockid, elementid]) +// CHECK-NEXT: 0 IN (global_var, [1, 2]) +// CHECK-NEXT: 0 IN (ptr, [1, 3]) +// CHECK-NEXT: 0 OUT (global_var, [1, 2]) +// CHECK-NEXT: 0 OUT (ptr, [1, 3]) +// CHECK-NEXT: 1 OUT (global_var, [1, 2]) +// CHECK-NEXT: 1 OUT (ptr, [1, 3]) + +void single_def_in_same_block() { + int *ptr = getIntPtr(); + + if (coin()) + ptr = 0; + + if (!ptr) + *ptr = 5; +} +// -> [B3] -> -> [B1] -> +// / \ / \ +// [B5 (ENTRY)] -> [B4] ------> [B2] ---> [B0 (EXIT)] + +// CHECK: GEN sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 3 (ptr, [3, 3]) +// CHECK-NEXT: 4 (global_var, [4, 6]) +// CHECK-NEXT: 4 (ptr, [4, 3]) +// CHECK-NEXT: KILL sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 3 (ptr, [4, 3]) +// CHECK-NEXT: 4 (ptr, [3, 3]) +// CHECK-NEXT: Reaching definition sets: blockid IN/OUT (varname [blockid, elementid]) +// CHECK-NEXT: 0 IN (global_var, [4, 6]) +// CHECK-NEXT: 0 IN (ptr, [3, 3]) +// CHECK-NEXT: 0 IN (ptr, [4, 3]) +// CHECK-NEXT: 0 OUT (global_var, [4, 6]) +// CHECK-NEXT: 0 OUT (ptr, [3, 3]) +// CHECK-NEXT: 0 OUT (ptr, [4, 3]) +// CHECK-NEXT: 1 IN (global_var, [4, 6]) +// CHECK-NEXT: 1 IN (ptr, [3, 3]) +// CHECK-NEXT: 1 IN (ptr, [4, 3]) +// CHECK-NEXT: 1 OUT (global_var, [4, 6]) +// CHECK-NEXT: 1 OUT (ptr, [3, 3]) +// CHECK-NEXT: 1 OUT (ptr, [4, 3]) +// CHECK-NEXT: 2 IN (global_var, [4, 6]) +// CHECK-NEXT: 2 IN (ptr, [3, 3]) +// CHECK-NEXT: 2 IN (ptr, [4, 3]) +// CHECK-NEXT: 2 OUT (global_var, [4, 6]) +// CHECK-NEXT: 2 OUT (ptr, [3, 3]) +// CHECK-NEXT: 2 OUT (ptr, [4, 3]) +// CHECK-NEXT: 3 IN (global_var, [4, 6]) +// CHECK-NEXT: 3 IN (ptr, [4, 3]) +// CHECK-NEXT: 3 OUT (global_var, [4, 6]) +// CHECK-NEXT: 3 OUT (ptr, [3, 3]) +// CHECK-NEXT: 4 OUT (global_var, [4, 6]) +// CHECK-NEXT: 4 OUT (ptr, [4, 3]) + +void different_assignments() { + int i = getInt(); + + if (coin()) + i = 0; + + i += 3; + + if (!coin()) + i -= 2; + + i *= 9; + + if (i = 0) + ; +} +// -> [B5] -> -> [B3] -> -> [B1] -> +// / \ / \ / \ +// [B7 (ENTRY)] -> [B6] ------> [B4] -------> [B2] ---> [B0 (EXIT)] + +// CHECK: GEN sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 2 (i, [2, 5]) +// CHECK-NEXT: 3 (i, [3, 2]) +// CHECK-NEXT: 4 (global_var, [4, 5]) +// CHECK-NEXT: 4 (i, [4, 2]) +// CHECK-NEXT: 5 (i, [5, 2]) +// CHECK-NEXT: 6 (global_var, [6, 6]) +// CHECK-NEXT: 6 (i, [6, 3]) +// CHECK-NEXT: KILL sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 2 (i, [3, 2]) +// CHECK-NEXT: 2 (i, [4, 2]) +// CHECK-NEXT: 2 (i, [5, 2]) +// CHECK-NEXT: 2 (i, [6, 3]) +// CHECK-NEXT: 3 (i, [2, 5]) +// CHECK-NEXT: 3 (i, [4, 2]) +// CHECK-NEXT: 3 (i, [5, 2]) +// CHECK-NEXT: 3 (i, [6, 3]) +// CHECK-NEXT: 4 (global_var, [6, 6]) +// CHECK-NEXT: 4 (i, [2, 5]) +// CHECK-NEXT: 4 (i, [3, 2]) +// CHECK-NEXT: 4 (i, [5, 2]) +// CHECK-NEXT: 4 (i, [6, 3]) +// CHECK-NEXT: 5 (i, [2, 5]) +// CHECK-NEXT: 5 (i, [3, 2]) +// CHECK-NEXT: 5 (i, [4, 2]) +// CHECK-NEXT: 5 (i, [6, 3]) +// CHECK-NEXT: 6 (global_var, [4, 5]) +// CHECK-NEXT: 6 (i, [2, 5]) +// CHECK-NEXT: 6 (i, [3, 2]) +// CHECK-NEXT: 6 (i, [4, 2]) +// CHECK-NEXT: 6 (i, [5, 2]) +// CHECK-NEXT: Reaching definition sets: blockid IN/OUT (varname [blockid, elementid]) +// CHECK-NEXT: 0 IN (global_var, [4, 5]) +// CHECK-NEXT: 0 IN (i, [2, 5]) +// CHECK-NEXT: 0 OUT (global_var, [4, 5]) +// CHECK-NEXT: 0 OUT (i, [2, 5]) +// CHECK-NEXT: 1 IN (global_var, [4, 5]) +// CHECK-NEXT: 1 IN (i, [2, 5]) +// CHECK-NEXT: 1 OUT (global_var, [4, 5]) +// CHECK-NEXT: 1 OUT (i, [2, 5]) +// CHECK-NEXT: 2 IN (global_var, [4, 5]) +// CHECK-NEXT: 2 IN (i, [3, 2]) +// CHECK-NEXT: 2 IN (i, [4, 2]) +// CHECK-NEXT: 2 OUT (global_var, [4, 5]) +// CHECK-NEXT: 2 OUT (i, [2, 5]) +// CHECK-NEXT: 3 IN (global_var, [4, 5]) +// CHECK-NEXT: 3 IN (i, [4, 2]) +// CHECK-NEXT: 3 OUT (global_var, [4, 5]) +// CHECK-NEXT: 3 OUT (i, [3, 2]) +// CHECK-NEXT: 4 IN (global_var, [6, 6]) +// CHECK-NEXT: 4 IN (i, [5, 2]) +// CHECK-NEXT: 4 IN (i, [6, 3]) +// CHECK-NEXT: 4 OUT (global_var, [4, 5]) +// CHECK-NEXT: 4 OUT (i, [4, 2]) +// CHECK-NEXT: 5 IN (global_var, [6, 6]) +// CHECK-NEXT: 5 IN (i, [6, 3]) +// CHECK-NEXT: 5 OUT (global_var, [6, 6]) +// CHECK-NEXT: 5 OUT (i, [5, 2]) +// CHECK-NEXT: 6 OUT (global_var, [6, 6]) +// CHECK-NEXT: 6 OUT (i, [6, 3]) + +namespace example_1 { + +int flag; + +void foo() { + flag = coin(); +} +// [B2 (ENTRY)] -> [B1] -> [B0 (EXIT)] + +// CHECK: GEN sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 1 (global_var, [1, 2]) +// CHECK-NEXT: 1 (flag, [1, 5]) +// CHECK-NEXT: KILL sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: Reaching definition sets: blockid IN/OUT (varname [blockid, elementid]) +// CHECK-NEXT: 0 IN (global_var, [1, 2]) +// CHECK-NEXT: 0 IN (flag, [1, 5]) +// CHECK-NEXT: 0 OUT (global_var, [1, 2]) +// CHECK-NEXT: 0 OUT (flag, [1, 5]) +// CHECK-NEXT: 1 OUT (global_var, [1, 2]) +// CHECK-NEXT: 1 OUT (flag, [1, 5]) + +void f() { + int *x = nullptr; + flag = 1; + + foo(); + if (flag) + x = new int; + + foo(); + if (flag) + *x = 5; +} +// -> [B3] -> -> [B1] -> +// / \ / \ +// [B5 (ENTRY)] -> [B4] ------> [B2] ---> [B0 (EXIT)] + +// CHECK: GEN sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 2 (global_var, [2, 2]) +// CHECK-NEXT: 2 (flag, [2, 2]) +// CHECK-NEXT: 3 (x, [3, 3]) +// CHECK-NEXT: 4 (global_var, [4, 8]) +// CHECK-NEXT: 4 (flag, [4, 8]) +// CHECK-NEXT: 4 (x, [4, 2]) +// CHECK-NEXT: KILL sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 2 (global_var, [4, 8]) +// CHECK-NEXT: 2 (flag, [4, 8]) +// CHECK-NEXT: 3 (x, [4, 2]) +// CHECK-NEXT: 4 (global_var, [2, 2]) +// CHECK-NEXT: 4 (flag, [2, 2]) +// CHECK-NEXT: 4 (x, [3, 3]) +// CHECK-NEXT: Reaching definition sets: blockid IN/OUT (varname [blockid, elementid]) +// CHECK-NEXT: 0 IN (global_var, [2, 2]) +// CHECK-NEXT: 0 IN (flag, [2, 2]) +// CHECK-NEXT: 0 IN (x, [3, 3]) +// CHECK-NEXT: 0 IN (x, [4, 2]) +// CHECK-NEXT: 0 OUT (global_var, [2, 2]) +// CHECK-NEXT: 0 OUT (flag, [2, 2]) +// CHECK-NEXT: 0 OUT (x, [3, 3]) +// CHECK-NEXT: 0 OUT (x, [4, 2]) +// CHECK-NEXT: 1 IN (global_var, [2, 2]) +// CHECK-NEXT: 1 IN (flag, [2, 2]) +// CHECK-NEXT: 1 IN (x, [3, 3]) +// CHECK-NEXT: 1 IN (x, [4, 2]) +// CHECK-NEXT: 1 OUT (global_var, [2, 2]) +// CHECK-NEXT: 1 OUT (flag, [2, 2]) +// CHECK-NEXT: 1 OUT (x, [3, 3]) +// CHECK-NEXT: 1 OUT (x, [4, 2]) +// CHECK-NEXT: 2 IN (global_var, [4, 8]) +// CHECK-NEXT: 2 IN (flag, [4, 8]) +// CHECK-NEXT: 2 IN (x, [3, 3]) +// CHECK-NEXT: 2 IN (x, [4, 2]) +// CHECK-NEXT: 2 OUT (global_var, [2, 2]) +// CHECK-NEXT: 2 OUT (flag, [2, 2]) +// CHECK-NEXT: 2 OUT (x, [3, 3]) +// CHECK-NEXT: 2 OUT (x, [4, 2]) +// CHECK-NEXT: 3 IN (global_var, [4, 8]) +// CHECK-NEXT: 3 IN (flag, [4, 8]) +// CHECK-NEXT: 3 IN (x, [4, 2]) +// CHECK-NEXT: 3 OUT (global_var, [4, 8]) +// CHECK-NEXT: 3 OUT (flag, [4, 8]) +// CHECK-NEXT: 3 OUT (x, [3, 3]) +// CHECK-NEXT: 4 OUT (global_var, [4, 8]) +// CHECK-NEXT: 4 OUT (flag, [4, 8]) +// CHECK-NEXT: 4 OUT (x, [4, 2]) + +} // end of namespace example_1 + +void assignment_buried_beneath_parentheses() { + int *ptr = getIntPtr(); + if (coin()) + (((((((((((((((ptr))))))) = getIntPtr())))))))); +} +// -> [B1] -> +// / \ +// [B3 (ENTRY)] -> [B2] ---> [B0 (EXIT)] + +// CHECK: GEN sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 1 (global_var, [1, 2]) +// CHECK-NEXT: 2 (global_var, [2, 6]) +// CHECK-NEXT: 2 (ptr, [2, 3]) +// CHECK-NEXT: KILL sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 1 (global_var, [2, 6]) +// CHECK-NEXT: 2 (global_var, [1, 2]) +// CHECK-NEXT: Reaching definition sets: blockid IN/OUT (varname [blockid, elementid]) +// CHECK-NEXT: 0 IN (global_var, [1, 2]) +// CHECK-NEXT: 0 IN (global_var, [2, 6]) +// CHECK-NEXT: 0 IN (ptr, [2, 3]) +// CHECK-NEXT: 0 OUT (global_var, [1, 2]) +// CHECK-NEXT: 0 OUT (global_var, [2, 6]) +// CHECK-NEXT: 0 OUT (ptr, [2, 3]) +// CHECK-NEXT: 1 IN (global_var, [2, 6]) +// CHECK-NEXT: 1 IN (ptr, [2, 3]) +// CHECK-NEXT: 1 OUT (global_var, [1, 2]) +// CHECK-NEXT: 1 OUT (ptr, [2, 3]) +// CHECK-NEXT: 2 OUT (global_var, [2, 6]) +// CHECK-NEXT: 2 OUT (ptr, [2, 3]) + +void single_parameter(int i) { + int *ptr = getIntPtr(); +} +// [B2 (ENTRY)] -> [B1] -> [B0 (EXIT)] + +// CHECK: GEN sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 1 (global_var, [1, 2]) +// CHECK-NEXT: 1 (ptr, [1, 3]) +// CHECK-NEXT: 2 (i, [2, 0]) +// CHECK-NEXT: KILL sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: Reaching definition sets: blockid IN/OUT (varname [blockid, elementid]) +// CHECK-NEXT: 0 IN (global_var, [1, 2]) +// CHECK-NEXT: 0 IN (i, [2, 0]) +// CHECK-NEXT: 0 IN (ptr, [1, 3]) +// CHECK-NEXT: 0 OUT (global_var, [1, 2]) +// CHECK-NEXT: 0 OUT (i, [2, 0]) +// CHECK-NEXT: 0 OUT (ptr, [1, 3]) +// CHECK-NEXT: 1 IN (i, [2, 0]) +// CHECK-NEXT: 1 OUT (global_var, [1, 2]) +// CHECK-NEXT: 1 OUT (i, [2, 0]) +// CHECK-NEXT: 1 OUT (ptr, [1, 3]) +// CHECK-NEXT: 2 OUT (i, [2, 0]) + +void multiple_parameters(int i, int j, int k) { + int *ptr = getIntPtr(); +} +// [B2 (ENTRY)] -> [B1] -> [B0 (EXIT)] + +// CHECK: GEN sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 1 (global_var, [1, 2]) +// CHECK-NEXT: 1 (ptr, [1, 3]) +// CHECK-NEXT: 2 (i, [2, 0]) +// CHECK-NEXT: 2 (j, [2, 0]) +// CHECK-NEXT: 2 (k, [2, 0]) +// CHECK-NEXT: KILL sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: Reaching definition sets: blockid IN/OUT (varname [blockid, elementid]) +// CHECK-NEXT: 0 IN (global_var, [1, 2]) +// CHECK-NEXT: 0 IN (i, [2, 0]) +// CHECK-NEXT: 0 IN (j, [2, 0]) +// CHECK-NEXT: 0 IN (k, [2, 0]) +// CHECK-NEXT: 0 IN (ptr, [1, 3]) +// CHECK-NEXT: 0 OUT (global_var, [1, 2]) +// CHECK-NEXT: 0 OUT (i, [2, 0]) +// CHECK-NEXT: 0 OUT (j, [2, 0]) +// CHECK-NEXT: 0 OUT (k, [2, 0]) +// CHECK-NEXT: 0 OUT (ptr, [1, 3]) +// CHECK-NEXT: 1 IN (i, [2, 0]) +// CHECK-NEXT: 1 IN (j, [2, 0]) +// CHECK-NEXT: 1 IN (k, [2, 0]) +// CHECK-NEXT: 1 OUT (global_var, [1, 2]) +// CHECK-NEXT: 1 OUT (i, [2, 0]) +// CHECK-NEXT: 1 OUT (j, [2, 0]) +// CHECK-NEXT: 1 OUT (k, [2, 0]) +// CHECK-NEXT: 1 OUT (ptr, [1, 3]) +// CHECK-NEXT: 2 OUT (i, [2, 0]) +// CHECK-NEXT: 2 OUT (j, [2, 0]) +// CHECK-NEXT: 2 OUT (k, [2, 0]) + +void assignment_and_declaration_on_same_line(int i, int j) { + int k = i = j = 0; +} +// [B2 (ENTRY)] -> [B1] -> [B0 (EXIT)] + +// CHECK: GEN sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 1 (i, [1, 5]) +// CHECK-NEXT: 1 (j, [1, 2]) +// CHECK-NEXT: 1 (k, [1, 7]) +// CHECK-NEXT: 2 (i, [2, 0]) +// CHECK-NEXT: 2 (j, [2, 0]) +// CHECK-NEXT: KILL sets: blockid (varname [blockid, elementid]) +// CHECK-NEXT: 1 (i, [2, 0]) +// CHECK-NEXT: 1 (j, [2, 0]) +// CHECK-NEXT: 2 (i, [1, 5]) +// CHECK-NEXT: 2 (j, [1, 2]) +// CHECK-NEXT: Reaching definition sets: blockid IN/OUT (varname [blockid, elementid]) +// CHECK-NEXT: 0 IN (i, [1, 5]) +// CHECK-NEXT: 0 IN (j, [1, 2]) +// CHECK-NEXT: 0 IN (k, [1, 7]) +// CHECK-NEXT: 0 OUT (i, [1, 5]) +// CHECK-NEXT: 0 OUT (j, [1, 2]) +// CHECK-NEXT: 0 OUT (k, [1, 7]) +// CHECK-NEXT: 1 IN (i, [2, 0]) +// CHECK-NEXT: 1 IN (j, [2, 0]) +// CHECK-NEXT: 1 OUT (i, [1, 5]) +// CHECK-NEXT: 1 OUT (j, [1, 2]) +// CHECK-NEXT: 1 OUT (k, [1, 7]) +// CHECK-NEXT: 2 OUT (i, [2, 0]) +// CHECK-NEXT: 2 OUT (j, [2, 0]) +