[cfe-commits] r45986 - in /cfe/trunk: Analysis/GREngine.cpp include/clang/Analysis/PathSensitive/ExplodedGraph.h include/clang/Analysis/PathSensitive/GREngine.h include/clang/Analysis/PathSensitive/GRWorkList.h
Ted Kremenek
kremenek at apple.com
Mon Jan 14 15:24:38 PST 2008
Author: kremenek
Date: Mon Jan 14 17:24:37 2008
New Revision: 45986
URL: http://llvm.org/viewvc/llvm-project?rev=45986&view=rev
Log:
Added prototype implementation of path-sens. analysis core engine.
Added:
cfe/trunk/Analysis/GREngine.cpp
cfe/trunk/include/clang/Analysis/PathSensitive/GREngine.h
cfe/trunk/include/clang/Analysis/PathSensitive/GRWorkList.h
Modified:
cfe/trunk/include/clang/Analysis/PathSensitive/ExplodedGraph.h
Added: cfe/trunk/Analysis/GREngine.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/Analysis/GREngine.cpp?rev=45986&view=auto
==============================================================================
--- cfe/trunk/Analysis/GREngine.cpp (added)
+++ cfe/trunk/Analysis/GREngine.cpp Mon Jan 14 17:24:37 2008
@@ -0,0 +1,261 @@
+//==- GREngine.cpp - Path-Sensitive Dataflow Engine ----------------*- C++ -*-//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines a generic engine for intraprocedural, path-sensitive,
+// dataflow analysis via graph reachability engine.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/PathSensitive/GREngine.h"
+#include "clang/AST/Expr.h"
+#include "llvm/Support/Compiler.h"
+#include "llvm/Support/Casting.h"
+#include "llvm/ADT/DenseMap.h"
+#include <vector>
+
+using llvm::cast;
+using llvm::isa;
+using namespace clang;
+
+namespace {
+ class VISIBILITY_HIDDEN DFS : public GRWorkList {
+ llvm::SmallVector<GRWorkListUnit,20> Stack;
+public:
+ virtual bool hasWork() const {
+ return !Stack.empty();
+ }
+
+ virtual void Enqueue(const GRWorkListUnit& U) {
+ Stack.push_back(U);
+ }
+
+ virtual GRWorkListUnit Dequeue() {
+ assert (!Stack.empty());
+ const GRWorkListUnit& U = Stack.back();
+ Stack.pop_back(); // This technically "invalidates" U, but we are fine.
+ return U;
+ }
+};
+} // end anonymous namespace
+
+GRWorkList* GRWorkList::MakeDFS() { return new DFS(); }
+
+/// ExecuteWorkList - Run the worklist algorithm for a maximum number of steps.
+bool GREngineImpl::ExecuteWorkList(unsigned Steps) {
+
+ if (G->num_roots() == 0) { // Initialize the analysis by constructing
+ // the root if none exists.
+
+ CFGBlock* Entry = &cfg.getEntry();
+
+ assert (Entry->empty() &&
+ "Entry block must be empty.");
+
+ assert (Entry->succ_size() == 1 &&
+ "Entry block must have 1 successor.");
+
+ // Get the solitary successor.
+ CFGBlock* Succ = *(Entry->succ_begin());
+
+ // Construct an edge representing the
+ // starting location in the function.
+ BlockEdge StartLoc(cfg, Entry, Succ);
+
+ // Generate the root.
+ GenerateNode(StartLoc, getInitialState());
+ }
+
+ while (Steps && WList->hasWork()) {
+ --Steps;
+ const GRWorkListUnit& WU = WList->Dequeue();
+ ExplodedNodeImpl* Node = WU.getNode();
+
+ // Dispatch on the location type.
+ switch (Node->getLocation().getKind()) {
+ default:
+ assert (isa<BlockEdge>(Node->getLocation()));
+ HandleBlockEdge(cast<BlockEdge>(Node->getLocation()), Node);
+ break;
+
+ case ProgramPoint::BlockEntranceKind:
+ HandleBlockEntrance(cast<BlockEntrance>(Node->getLocation()), Node);
+ break;
+
+ case ProgramPoint::BlockExitKind:
+ HandleBlockExit(cast<BlockExit>(Node->getLocation()), Node);
+ break;
+
+ case ProgramPoint::PostStmtKind:
+ HandlePostStmt(cast<PostStmt>(Node->getLocation()), WU.getBlock(),
+ WU.getIndex(), Node);
+ break;
+ }
+ }
+
+ return WList->hasWork();
+}
+
+void GREngineImpl::HandleBlockEdge(const BlockEdge& L, ExplodedNodeImpl* Pred) {
+
+ CFGBlock* Blk = L.getDst();
+
+ // Check if we are entering the EXIT block.
+ if (Blk == &cfg.getExit()) {
+
+ assert (cfg.getExit().size() == 0 && "EXIT block cannot contain Stmts.");
+
+ // Process the final state transition.
+ void* State = ProcessEOP(Blk, Pred->State);
+
+ bool IsNew;
+ ExplodedNodeImpl* Node = G->getNodeImpl(BlockEntrance(Blk), State, &IsNew);
+ Node->addPredecessor(Pred);
+
+ // If the node was freshly created, mark it as an "End-Of-Path" node.
+ if (IsNew) G->addEndOfPath(Node);
+
+ // This path is done. Don't enqueue any more nodes.
+ return;
+ }
+
+ // FIXME: we will dispatch to a function that
+ // manipulates the state at the entrance to a block.
+
+ if (!Blk->empty())
+ GenerateNode(BlockEntrance(Blk), Pred->State, Pred);
+ else
+ GenerateNode(BlockExit(Blk), Pred->State, Pred);
+}
+
+void GREngineImpl::HandleBlockEntrance(const BlockEntrance& L,
+ ExplodedNodeImpl* Pred) {
+
+ if (Stmt* S = L.getFirstStmt()) {
+ GRNodeBuilderImpl Builder(L.getBlock(), 0, Pred, this);
+ ProcessStmt(S, Builder);
+ }
+ else
+ GenerateNode(BlockExit(L.getBlock()), Pred->State, Pred);
+}
+
+
+void GREngineImpl::HandleBlockExit(const BlockExit& L, ExplodedNodeImpl* Pred) {
+
+ CFGBlock* B = L.getBlock();
+
+ if (Stmt* Terminator = B->getTerminator())
+ ProcessTerminator(Terminator, B, Pred);
+ else {
+ assert (B->succ_size() == 1 &&
+ "Blocks with no terminator should have at most 1 successor.");
+
+ GenerateNode(BlockEdge(cfg,B,*(B->succ_begin())), Pred->State, Pred);
+ }
+}
+
+void GREngineImpl::HandlePostStmt(const PostStmt& L, CFGBlock* B,
+ unsigned StmtIdx, ExplodedNodeImpl* Pred) {
+
+ assert (!B->empty());
+
+ if (StmtIdx == B->size()) {
+ // FIXME: This is essentially an epsilon-transition. Do we need it?
+ // It does simplify the logic, and it is also another point
+ // were we could introduce a dispatch to the client.
+ GenerateNode(BlockExit(B), Pred->State, Pred);
+ }
+ else {
+ GRNodeBuilderImpl Builder(B, StmtIdx, Pred, this);
+ ProcessStmt(L.getStmt(), Builder);
+ }
+}
+
+typedef llvm::DenseMap<Stmt*,Stmt*> ParentMapTy;
+/// PopulateParentMap - Recurse the AST starting at 'Parent' and add the
+/// mappings between child and parent to ParentMap.
+static void PopulateParentMap(Stmt* Parent, ParentMapTy& M) {
+ for (Stmt::child_iterator I=Parent->child_begin(),
+ E=Parent->child_end(); I!=E; ++I) {
+
+ assert (M.find(*I) == M.end());
+ M[*I] = Parent;
+ PopulateParentMap(*I, M);
+ }
+}
+
+/// GenerateNode - Utility method to generate nodes, hook up successors,
+/// and add nodes to the worklist.
+void GREngineImpl::GenerateNode(const ProgramPoint& Loc, void* State,
+ ExplodedNodeImpl* Pred) {
+
+ bool IsNew;
+ ExplodedNodeImpl* Node = G->getNodeImpl(Loc, State, &IsNew);
+
+ if (Pred)
+ Node->addPredecessor(Pred); // Link 'Node' with its predecessor.
+ else {
+ assert (IsNew);
+ G->addRoot(Node); // 'Node' has no predecessor. Make it a root.
+ }
+
+ // Only add 'Node' to the worklist if it was freshly generated.
+ if (IsNew) WList->Enqueue(GRWorkListUnit(Node));
+}
+
+GRNodeBuilderImpl::GRNodeBuilderImpl(CFGBlock* b, unsigned idx,
+ ExplodedNodeImpl* N, GREngineImpl* e)
+ : Eng(*e), B(*b), Idx(idx), LastNode(N), Populated(false) {
+ Deferred.insert(N);
+}
+
+GRNodeBuilderImpl::~GRNodeBuilderImpl() {
+ for (DeferredTy::iterator I=Deferred.begin(), E=Deferred.end(); I!=E; ++I)
+ if (!(*I)->isInfeasible())
+ GenerateAutoTransition(*I);
+}
+
+void GRNodeBuilderImpl::GenerateAutoTransition(ExplodedNodeImpl* N) {
+ assert (!N->isInfeasible());
+
+ PostStmt Loc(getStmt());
+
+ if (Loc == N->getLocation()) {
+ // Note: 'N' should be a fresh node because otherwise it shouldn't be
+ // a member of Deferred.
+ Eng.WList->Enqueue(N, B, Idx+1);
+ return;
+ }
+
+ bool IsNew;
+ ExplodedNodeImpl* Succ = Eng.G->getNodeImpl(Loc, N->State, &IsNew);
+ Succ->addPredecessor(N);
+
+ if (IsNew)
+ Eng.WList->Enqueue(Succ, B, Idx+1);
+}
+
+ExplodedNodeImpl* GRNodeBuilderImpl::generateNodeImpl(Stmt* S, void* State,
+ ExplodedNodeImpl* Pred) {
+
+ bool IsNew;
+ ExplodedNodeImpl* N = Eng.G->getNodeImpl(PostStmt(S), State, &IsNew);
+ N->addPredecessor(Pred);
+ Deferred.erase(Pred);
+
+ HasGeneratedNode = true;
+
+ if (IsNew) {
+ Deferred.insert(N);
+ LastNode = N;
+ return N;
+ }
+
+ LastNode = NULL;
+ return NULL;
+}
Modified: cfe/trunk/include/clang/Analysis/PathSensitive/ExplodedGraph.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Analysis/PathSensitive/ExplodedGraph.h?rev=45986&r1=45985&r2=45986&view=diff
==============================================================================
--- cfe/trunk/include/clang/Analysis/PathSensitive/ExplodedGraph.h (original)
+++ cfe/trunk/include/clang/Analysis/PathSensitive/ExplodedGraph.h Mon Jan 14 17:24:37 2008
@@ -28,13 +28,16 @@
class GREngineImpl;
class ExplodedNodeImpl;
+class GRNodeBuilderImpl;
class ExplodedNodeImpl : public llvm::FoldingSetNode {
protected:
friend class ExplodedGraphImpl;
+ friend class GREngineImpl;
+ friend class GRNodeBuilderImpl;
class NodeGroup {
- enum { Size1 = 0x0, SizeOther = 0x1, Flags = 0x1 };
+ enum { Size1 = 0x0, SizeOther = 0x1, Infeasible = 0x2, Flags = 0x3 };
uintptr_t P;
unsigned getKind() const { return P & Flags; }
@@ -55,6 +58,14 @@
bool empty() const;
void addNode(ExplodedNodeImpl* N);
+
+ void setInfeasibleFlag() {
+ P |= Infeasible;
+ }
+
+ bool getInfeasibleFlag() const {
+ return P & Infeasible ? true : false;
+ }
};
@@ -78,9 +89,9 @@
: Location(loc), State(state) {}
/// addPredeccessor - Adds a predecessor to the current node, and
- /// in tandem add this node as a successor of the other node. This
- /// method is intended to be used only by ExplodedGraphImpl.
+ /// in tandem add this node as a successor of the other node.
void addPredecessor(ExplodedNodeImpl* V) {
+ assert (!V->isInfeasible());
Preds.addNode(V);
V->Succs.addNode(this);
}
@@ -103,6 +114,9 @@
unsigned pred_size() const { return Preds.size(); }
bool succ_empty() const { return Succs.empty(); }
bool pred_empty() const { return Preds.size(); }
+
+ bool isInfeasible() const { return Preds.getInfeasibleFlag(); }
+ void setInfeasible() { Preds.setInfeasibleFlag(); }
};
@@ -166,6 +180,7 @@
class ExplodedGraphImpl {
protected:
friend class GREngineImpl;
+ friend class GRNodeBuilderImpl;
// Type definitions.
typedef llvm::DenseMap<ProgramPoint,void*> EdgeNodeSetMap;
Added: cfe/trunk/include/clang/Analysis/PathSensitive/GREngine.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Analysis/PathSensitive/GREngine.h?rev=45986&view=auto
==============================================================================
--- cfe/trunk/include/clang/Analysis/PathSensitive/GREngine.h (added)
+++ cfe/trunk/include/clang/Analysis/PathSensitive/GREngine.h Mon Jan 14 17:24:37 2008
@@ -0,0 +1,228 @@
+//==- GREngine.h - Path-Sensitive Dataflow Engine ------------------*- C++ -*-//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines a generic engine for intraprocedural, path-sensitive,
+// dataflow analysis via graph reachability engine.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_ANALYSIS_GRENGINE
+#define LLVM_CLANG_ANALYSIS_GRENGINE
+
+#include "clang/Analysis/PathSensitive/ExplodedGraph.h"
+#include "clang/Analysis/PathSensitive/GRWorkList.h"
+#include "llvm/ADT/OwningPtr.h"
+
+namespace clang {
+
+class CFG;
+class GRNodeBuilderImpl;
+class GRWorkList;
+
+class GREngineImpl {
+protected:
+ friend class GRNodeBuilderImpl;
+
+ typedef llvm::DenseMap<Stmt*,Stmt*> ParentMapTy;
+
+ /// cfg - The control-flow graph of the function being analyzed.
+ CFG& cfg;
+
+ /// G - The simulation graph. Each node is a (location,state) pair.
+ llvm::OwningPtr<ExplodedGraphImpl> G;
+
+ /// ParentMap - A lazily populated map from a Stmt* to its parent Stmt*.
+ void* ParentMap;
+
+ /// CurrentBlkExpr - The current Block-level expression being processed.
+ /// This is used when lazily populating ParentMap.
+ Stmt* CurrentBlkExpr;
+
+ /// WList - A set of queued nodes that need to be processed by the
+ /// worklist algorithm. It is up to the implementation of WList to decide
+ /// the order that nodes are processed.
+ GRWorkList* WList;
+
+ void GenerateNode(const ProgramPoint& Loc, void* State,
+ ExplodedNodeImpl* Pred = NULL);
+
+ /// getInitialState - Gets the void* representing the initial 'state'
+ /// of the analysis. This is simply a wrapper (implemented
+ /// in GREngine) that performs type erasure on the initial
+ /// state returned by the checker object.
+ virtual void* getInitialState() = 0;
+
+ void HandleBlockEdge(const BlockEdge& E, ExplodedNodeImpl* Pred);
+ void HandleBlockEntrance(const BlockEntrance& E, ExplodedNodeImpl* Pred);
+ void HandleBlockExit(const BlockExit& E, ExplodedNodeImpl* Pred);
+ void HandlePostStmt(const PostStmt& S, CFGBlock* B,
+ unsigned StmtIdx, ExplodedNodeImpl *Pred);
+
+ virtual void* ProcessEOP(CFGBlock* Blk, void* State) = 0;
+
+ virtual void ProcessStmt(Stmt* S, GRNodeBuilderImpl& Builder) = 0;
+
+ virtual void ProcessTerminator(Stmt* Terminator, CFGBlock* B,
+ ExplodedNodeImpl* Pred) = 0;
+
+private:
+ GREngineImpl(const GREngineImpl&); // Do not implement.
+ GREngineImpl& operator=(const GREngineImpl&);
+
+protected:
+ GREngineImpl(CFG& c, ExplodedGraphImpl* g, GRWorkList* wl)
+ : cfg(c), G(g), WList(wl) {}
+
+public:
+ /// ExecuteWorkList - Run the worklist algorithm for a maximum number of
+ /// steps. Returns true if there is still simulation state on the worklist.
+ bool ExecuteWorkList(unsigned Steps = 1000000);
+
+ virtual ~GREngineImpl() {}
+};
+
+class GRNodeBuilderImpl {
+ GREngineImpl& Eng;
+ CFGBlock& B;
+ const unsigned Idx;
+ ExplodedNodeImpl* LastNode;
+ bool HasGeneratedNode;
+ bool Populated;
+
+ typedef llvm::SmallPtrSet<ExplodedNodeImpl*,5> DeferredTy;
+ DeferredTy Deferred;
+
+ void GenerateAutoTransition(ExplodedNodeImpl* N);
+
+public:
+ GRNodeBuilderImpl(CFGBlock* b, unsigned idx,
+ ExplodedNodeImpl* N, GREngineImpl* e);
+
+ ~GRNodeBuilderImpl();
+
+ const ExplodedGraphImpl& getGraph() const { return *Eng.G; }
+
+ inline ExplodedNodeImpl* getLastNode() {
+ return LastNode ? (LastNode->isInfeasible() ? NULL : LastNode) : NULL;
+ }
+
+ ExplodedNodeImpl* generateNodeImpl(Stmt* S, void* State,
+ ExplodedNodeImpl* Pred);
+
+ inline ExplodedNodeImpl* generateNodeImpl(Stmt* S, void* State) {
+ ExplodedNodeImpl* N = getLastNode();
+ assert (N && "Predecessor of new node is infeasible.");
+ return generateNodeImpl(S, State, N);
+ }
+
+ Stmt* getStmt() const { return B[Idx]; }
+
+ CFGBlock* getBlock() const { return &B; }
+};
+
+template<typename CHECKER>
+class GRNodeBuilder {
+ typedef CHECKER CheckerTy;
+ typedef typename CheckerTy::StateTy StateTy;
+ typedef ExplodedGraph<CheckerTy> GraphTy;
+ typedef typename GraphTy::NodeTy NodeTy;
+
+ GRNodeBuilderImpl& NB;
+
+public:
+ const GraphTy& getGraph() const {
+ return static_cast<const GraphTy&>(NB.getGraph());
+ }
+
+ NodeTy* getLastNode() const {
+ return static_cast<NodeTy*>(NB.getLastNode());
+ }
+
+ NodeTy* generateNode(Stmt* S, StateTy State, NodeTy* Pred) {
+ void *state = GRTrait<StateTy>::toPtr(State);
+ return static_cast<NodeTy*>(NB.generateNodeImpl(S, state, Pred));
+ }
+
+ NodeTy* generateNode(Stmt* S, StateTy State) {
+ void *state = GRTrait<StateTy>::toPtr(State);
+ return static_cast<NodeTy*>(NB.generateNodeImpl(S, state));
+ }
+};
+
+
+template<typename CHECKER>
+class GREngine : public GREngineImpl {
+public:
+ typedef CHECKER CheckerTy;
+ typedef typename CheckerTy::StateTy StateTy;
+ typedef ExplodedGraph<CheckerTy> GraphTy;
+ typedef typename GraphTy::NodeTy NodeTy;
+
+protected:
+ // A local reference to the checker that avoids an indirect access
+ // via the Graph.
+ CheckerTy* Checker;
+
+
+ virtual void* getInitialState() {
+ return GRTrait<StateTy>::toPtr(getCheckerState()->getInitialState());
+ }
+
+ virtual void* ProcessEOP(CFGBlock* Blk, void* State) {
+ // FIXME: Perform dispatch to adjust state.
+ return State;
+ }
+
+ virtual void ProcessStmt(Stmt* S, GRNodeBuilderImpl& BuilderImpl) {
+ GRNodeBuilder<CHECKER> Builder(BuilderImpl);
+ Checker->ProcessStmt(S, Builder);
+ }
+
+ virtual void ProcessTerminator(Stmt* Terminator, CFGBlock* B,
+ ExplodedNodeImpl* Pred) {
+ // FIXME: Dispatch.
+ }
+
+
+public:
+ /// Construct a GREngine object to analyze the provided CFG using
+ /// a DFS exploration of the exploded graph.
+ GREngine(CFG& Cfg)
+ : GREngineImpl(cfg, new GraphTy(), GRWorkList::MakeDFS()),
+ Checker(static_cast<GraphTy*>(G.get())->getCheckerState()) {}
+
+ /// Construct a GREngine object to analyze the provided CFG and to
+ /// use the provided worklist object to execute the worklist algorithm.
+ /// The GREngine object assumes ownership of 'wlist'.
+ GREngine(CFG& cfg, GRWorkList* wlist)
+ : GREngineImpl(cfg, new GraphTy(), wlist),
+ Checker(static_cast<GraphTy*>(G.get())->getCheckerState()) {}
+
+ virtual ~GREngine() {}
+
+ /// getGraph - Returns the exploded graph.
+ GraphTy& getGraph() {
+ return *static_cast<GraphTy*>(G.get());
+ }
+
+ /// getCheckerState - Returns the internal checker state.
+ CheckerTy& getCheckerState() {
+ return *Checker;
+ }
+
+ /// takeGraph - Returns the exploded graph. Ownership of the graph is
+ /// transfered to the caller.
+ GraphTy* takeGraph() {
+ return static_cast<GraphTy*>(G.take());
+ }
+};
+
+} // end clang namespace
+
+#endif
Added: cfe/trunk/include/clang/Analysis/PathSensitive/GRWorkList.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Analysis/PathSensitive/GRWorkList.h?rev=45986&view=auto
==============================================================================
--- cfe/trunk/include/clang/Analysis/PathSensitive/GRWorkList.h (added)
+++ cfe/trunk/include/clang/Analysis/PathSensitive/GRWorkList.h Mon Jan 14 17:24:37 2008
@@ -0,0 +1,54 @@
+//==- GRWorkList.h - Worklist class used by GREngine ---------------*- C++ -*-//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines GRWorkList, a pure virtual class that represents an
+// opaque worklist used by GREngine to explore the reachability state space.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_ANALYSIS_GRWORKLIST
+#define LLVM_CLANG_ANALYSIS_GRWORKLIST
+
+namespace clang {
+
+class ExplodedNodeImpl;
+
+class GRWorkListUnit {
+ ExplodedNodeImpl* Node;
+ CFGBlock* Block;
+ unsigned BlockIdx;
+
+public:
+ GRWorkListUnit(ExplodedNodeImpl* N, CFGBlock* B, unsigned idx)
+ : Node(N), Block(B), BlockIdx(idx) {}
+
+ explicit GRWorkListUnit(ExplodedNodeImpl* N)
+ : Node(N), Block(NULL), BlockIdx(0) {}
+
+ ExplodedNodeImpl* getNode() const { return Node; }
+ CFGBlock* getBlock() const { return Block; }
+ unsigned getIndex() const { return BlockIdx; }
+};
+
+class GRWorkList {
+public:
+ virtual ~GRWorkList() = 0;
+ virtual bool hasWork() const = 0;
+ virtual void Enqueue(const GRWorkListUnit& U) = 0;
+
+ void Enqueue(ExplodedNodeImpl* N, CFGBlock& B, unsigned idx) {
+ Enqueue(GRWorkListUnit(N,&B,idx));
+ }
+
+ virtual GRWorkListUnit Dequeue() = 0;
+
+ static GRWorkList* MakeDFS();
+};
+} // end clang namespace
+#endif
More information about the cfe-commits
mailing list