Static Value-Flow Analysis
Loading...
Searching...
No Matches
PointerAnalysisImpl.cpp
Go to the documentation of this file.
1//===- PointerAnalysisImpl.cpp -- Pointer analysis implementation--------------------//
2//
3// SVF: Static Value-Flow Analysis
4//
5// Copyright (C) <2013-> <Yulei Sui>
6//
7
8// This program is free software: you can redistribute it and/or modify
9// it under the terms of the GNU Affero General Public License as published by
10// the Free Software Foundation, either version 3 of the License, or
11// (at your option) any later version.
12
13// This program is distributed in the hope that it will be useful,
14// but WITHOUT ANY WARRANTY; without even the implied warranty of
15// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16// GNU Affero General Public License for more details.
17
18// You should have received a copy of the GNU Affero General Public License
19// along with this program. If not, see <http://www.gnu.org/licenses/>.
20//
21//===----------------------------------------------------------------------===//
22
23
24/*
25 * PointerAnalysisImpl.cpp
26 *
27 * Created on: 28Mar.,2020
28 * Author: yulei
29 */
30
31
33#include "MemoryModel/PTATY.h"
35#include "Util/Options.h"
36#include <fstream>
37#include <sstream>
38
39#include "Graphs/CallGraph.h"
40
41using namespace SVF;
42using namespace SVFUtil;
43using namespace std;
44
49 PointerAnalysis(p, type, alias_check), ptCache()
50{
54 {
55 // Only maintain reverse points-to when the analysis is field-sensitive, as objects turning
56 // field-insensitive is all it is used for.
58 if (Options::ptDataBacking() == PTBackingType::Mutable) ptD = std::make_unique<MutDiffPTDataTy>(maintainRevPts);
59 else if (Options::ptDataBacking() == PTBackingType::Persistent) ptD = std::make_unique<PersDiffPTDataTy>(getPtCache(), maintainRevPts);
60 else assert(false && "BVDataPTAImpl::BVDataPTAImpl: unexpected points-to backing type!");
61 }
62 else if (type == PTATY::Steensgaard_WPA)
63 {
64 // Steensgaard is only field-insensitive (for now?), so no reverse points-to.
65 if (Options::ptDataBacking() == PTBackingType::Mutable) ptD = std::make_unique<MutDiffPTDataTy>(false);
66 else if (Options::ptDataBacking() == PTBackingType::Persistent) ptD = std::make_unique<PersDiffPTDataTy>(getPtCache(), false);
67 else assert(false && "BVDataPTAImpl::BVDataPTAImpl: unexpected points-to backing type!");
68 }
69 else if (type == PTATY::FSSPARSE_WPA)
70 {
72 {
73 if (Options::ptDataBacking() == PTBackingType::Mutable) ptD = std::make_unique<MutIncDFPTDataTy>(false);
74 else if (Options::ptDataBacking() == PTBackingType::Persistent) ptD = std::make_unique<PersIncDFPTDataTy>(getPtCache(), false);
75 else assert(false && "BVDataPTAImpl::BVDataPTAImpl: unexpected points-to backing type!");
76 }
77 else
78 {
79 if (Options::ptDataBacking() == PTBackingType::Mutable) ptD = std::make_unique<MutDFPTDataTy>(false);
80 else if (Options::ptDataBacking() == PTBackingType::Persistent) ptD = std::make_unique<PersDFPTDataTy>(getPtCache(), false);
81 else assert(false && "BVDataPTAImpl::BVDataPTAImpl: unexpected points-to backing type!");
82 }
83 }
84 else if (type == PTATY::VFS_WPA)
85 {
86 if (Options::ptDataBacking() == PTBackingType::Mutable) ptD = std::make_unique<MutVersionedPTDataTy>(false);
87 else if (Options::ptDataBacking() == PTBackingType::Persistent) ptD = std::make_unique<PersVersionedPTDataTy>(getPtCache(), false);
88 else assert(false && "BVDataPTAImpl::BVDataPTAImpl: unexpected points-to backing type!");
89 }
90 else assert(false && "no points-to data available");
91
93}
94
96{
99
101 {
102 std::string moduleName(pag->getModuleIdentifier());
103 std::vector<std::string> names = SVFUtil::split(moduleName,'/');
104 if (names.size() > 1)
105 moduleName = names[names.size() - 1];
106
107 std::string subtitle;
108
110 subtitle = "Andersen's analysis bitvector";
112 subtitle = "flow-sensitive analysis bitvector";
114 subtitle = "CFL analysis bitvector";
115 else if(ptaTy == PTATY::TypeCPP_WPA)
116 subtitle = "Type analysis bitvector";
118 subtitle = "DDA bitvector";
119 else
120 subtitle = "bitvector";
121
122 SVFUtil::outs() << "\n****Persistent Points-To Cache Statistics: " << subtitle << "****\n";
123 SVFUtil::outs() << "################ (program : " << moduleName << ")###############\n";
124 SVFUtil::outs().flags(std::ios::left);
125 ptCache.printStats("bitvector");
126 SVFUtil::outs() << "#######################################################" << std::endl;
127 SVFUtil::outs().flush();
128 }
129
130}
131
136{
137 expandedPts = pts;;
138 for(PointsTo::iterator pit = pts.begin(), epit = pts.end(); pit!=epit; ++pit)
139 {
141 {
143 }
144 }
145}
146
148{
150 for (const NodeID o : pts)
151 {
153 {
155 }
156 }
157}
158
160{
161 getPTDataTy()->remapAllPts();
162}
163
165{
166 outs() << "Storing ObjVar to '" << filename << "'...";
168 std::fstream f(filename.c_str(), std::ios_base::out);
169 if (!f.good())
170 {
171 outs() << " error opening file for writing!\n";
172 return;
173 }
174
175 // Write BaseNodes insensitivity to file
177 for (auto it = pag->begin(), ie = pag->end(); it != ie; ++it)
178 {
179 PAGNode* pagNode = it->second;
180 if (!isa<ObjVar>(pagNode)) continue;
181 NodeID n = pag->getBaseObjVarID(it->first);
182 if (NodeIDs.test(n)) continue;
183 f << n << " ";
184 f << isFieldInsensitive(n) << "\n";
185 NodeIDs.set(n);
186 }
187
188 f << "------\n";
189
190 f.close();
191 if (f.good())
192 {
193 outs() << "\n";
194 return;
195 }
196
197
198}
199
201{
202 // Write analysis results to file
203 for (auto it = pag->begin(), ie = pag->end(); it != ie; ++it)
204 {
205 NodeID var = it->first;
206 const PointsTo &pts = getPts(var);
207
209 f << var << " -> { ";
210 if (pts.empty())
211 {
212 f << " ";
213 }
214 else
215 {
216 for (NodeID n: pts)
217 {
218 f << n << " ";
219 }
220 }
221 f << "}\n";
222 }
223
224}
225
227{
228 //write gepObjVarMap to file(in form of: baseID offset gepObjNodeId)
230 for(SVFIR::OffsetToGepVarMap::const_iterator it = gepObjVarMap.begin(), eit = gepObjVarMap.end(); it != eit; it++)
231 {
232 const SVFIR::GepOffset offsetPair = it -> first;
233 //write the base id to file
234 f << offsetPair.first << " ";
235 //write the offset to file
236 f << offsetPair.second << " ";
237 //write the gepObjNodeId to file
238 f << it->second << "\n";
239 }
240
241}
242
249{
250
251 outs() << "Storing pointer analysis results to '" << filename << "'...";
252
254 std::fstream f(filename.c_str(), std::ios_base::app);
255 if (!f.good())
256 {
257 outs() << " error opening file for writing!\n";
258 return;
259 }
260
262
263 f << "------\n";
264
266
267 f << "------\n";
268
269 // Write BaseNodes insensitivity to file
271 for (auto it = pag->begin(), ie = pag->end(); it != ie; ++it)
272 {
273 PAGNode* pagNode = it->second;
274 if (!isa<ObjVar>(pagNode)) continue;
275 NodeID n = pag->getBaseObjVarID(it->first);
276 if (NodeIDs.test(n)) continue;
277 f << n << " ";
278 f << isFieldInsensitive(n) << "\n";
279 NodeIDs.set(n);
280 }
281
282 // Job finish and close file
283 f.close();
284 if (f.good())
285 {
286 outs() << "\n";
287 return;
288 }
289}
290
292{
293 string line;
294 // Read analysis results from file
296
297 // Read points-to sets
298 string delimiter1 = " -> { ";
299 string delimiter2 = " }";
302
303 while (F.good())
304 {
305 // Parse a single line in the form of "var -> { obj1 obj2 obj3 }"
306 getline(F, line);
307 if (line.at(0) == '[' || line == "---VERSIONED---") continue;
308 if (line == "------") break;
309 size_t pos = line.find(delimiter1);
310 if (pos == string::npos) break;
311 if (line.back() != '}') break;
312
313 // var
314 NodeID var = atoi(line.substr(0, pos).c_str());
315
316 // objs
317 pos = pos + delimiter1.length();
318 size_t len = line.length() - pos - delimiter2.length();
319 string objs = line.substr(pos, len);
321
322 if (!objs.empty())
323 {
324 // map the variable ID to its unique string pointer set
326 if (strPtsMap.count(objs)) continue;
327
329 NodeID obj;
330 while (ss.good())
331 {
332 ss >> obj;
333 dstPts.set(obj);
334 }
335 // map the string pointer set to the parsed PointsTo set
337 }
338 }
339
340 // map the variable ID to its pointer set
341 for (auto t: nodePtsMap)
342 ptD->unionPts(t.first, strPtsMap[t.second]);
343}
344
346{
347 string line;
348 //read GepObjVarMap from file
350 while (F.good())
351 {
352 getline(F, line);
353 if (line == "------") break;
354 // Parse a single line in the form of "ID baseNodeID offset"
356 NodeID base;
357 size_t offset;
358 NodeID id;
359 ss >> base >> offset >>id;
360 SVFIR::OffsetToGepVarMap::const_iterator iter = gepObjVarMap.find(std::make_pair(base, offset));
361 if (iter == gepObjVarMap.end())
362 {
363 const SVFVar* node = pag->getSVFVar(base);
364 const BaseObjVar* obj = nullptr;
365 if (const GepObjVar* gepObjVar = SVFUtil::dyn_cast<GepObjVar>(node))
366 {
367 obj = gepObjVar->getBaseObj();
368 }
369 else if (const BaseObjVar* baseNode = SVFUtil::dyn_cast<BaseObjVar>(node))
370 {
371 obj = baseNode;
372 }
373 else if (const DummyObjVar* baseNode = SVFUtil::dyn_cast<DummyObjVar>(node))
374 {
375 obj = baseNode;
376 }
377 else
378 assert(false && "new gep obj node kind?");
379 pag->addGepObjNode( obj, offset, id);
381 }
382
383
384 }
385}
386
387void BVDataPTAImpl::readAndSetObjFieldSensitivity(std::ifstream& F, const std::string& delimiterStr)
388{
389 string line;
390 // //update ObjVar status
391 while (F.good())
392 {
393 getline(F, line);
394 if (line.empty() || line == delimiterStr)
395 break;
396 // Parse a single line in the form of "baseNodeID insensitive"
398 NodeID base;
399 bool insensitive;
400 ss >> base >> insensitive;
401
402 if (insensitive)
404 }
405
406}
407
414{
415
416 outs() << "Loading pointer analysis results from '" << filename << "'...";
417
418 ifstream F(filename.c_str());
419 if (!F.is_open())
420 {
421 outs() << " error opening file for reading!\n";
422 return false;
423 }
424
426
428
430
432
433 // Update callgraph
435
436 F.close();
437 outs() << "\n";
438
439 return true;
440}
441
442
447{
448 for (OrderedNodeSet::iterator nIter = this->getAllValidPtrs().begin();
449 nIter != this->getAllValidPtrs().end(); ++nIter)
450 {
451 const SVFVar* node = getPAG()->getSVFVar(*nIter);
452 if (getPAG()->isValidTopLevelPtr(node))
453 {
454 const PointsTo& pts = this->getPts(node->getId());
455 outs() << "\nNodeID " << node->getId() << " ";
456
457 if (pts.empty())
458 {
459 outs() << "\t\tPointsTo: {empty}\n\n";
460 }
461 else
462 {
463 outs() << "\t\tPointsTo: { ";
464 for (PointsTo::iterator it = pts.begin(), eit = pts.end();
465 it != eit; ++it)
466 outs() << *it << " ";
467 outs() << "}\n\n";
468 }
469 }
470 }
471
472 outs().flush();
473}
474
475
480{
482 for(SVFIR::iterator it = pag->begin(), eit = pag->end(); it!=eit; it++)
483 {
484 pagNodes.insert(it->first);
485 }
486
487 for (NodeID n : pagNodes)
488 {
489 outs() << "----------------------------------------------\n";
490 dumpPts(n, this->getPts(n));
491 }
492
493 outs() << "----------------------------------------------\n";
494}
495
496
503{
504 for(CallSiteToFunPtrMap::const_iterator iter = callsites.begin(), eiter = callsites.end(); iter!=eiter; ++iter)
505 {
506 const CallICFGNode* cs = iter->first;
507
508 if (cs->isVirtualCall())
509 {
510 const SVFVar* vtbl = cs->getVtablePtr();
511 assert(vtbl != nullptr);
512 NodeID vtblId = vtbl->getId();
514 }
515 else
516 resolveIndCalls(iter->first,getPts(iter->second),newEdges);
517 }
518}
519
527{
528 // add indirect fork edges
529 if(ThreadCallGraph *tdCallGraph = SVFUtil::dyn_cast<ThreadCallGraph>(callgraph))
530 {
531 for(ThreadCallGraph::CallSiteSet::const_iterator it = tdCallGraph->forksitesBegin(),
532 eit = tdCallGraph->forksitesEnd(); it != eit; ++it)
533 {
534 const ValVar* pVar = tdCallGraph->getThreadAPI()->getForkedFun(*it);
535 if(SVFUtil::dyn_cast<FunValVar>(pVar) == nullptr)
536 {
537 SVFIR *pag = this->getPAG();
538 const NodeBS targets = this->getPts(pVar->getId()).toNodeBS();
539 for(NodeBS::iterator ii = targets.begin(), ie = targets.end(); ii != ie; ++ii)
540 {
541 if(const ObjVar *objPN = pag->getObjVar(*ii))
542 {
543 const BaseObjVar* obj = pag->getBaseObject(objPN->getId());
544 if(obj->isFunction())
545 {
546 const FunObjVar *svfForkedFun = SVFUtil::cast<FunObjVar>(obj)->getFunction();
547 if(tdCallGraph->addIndirectForkEdge(*it, svfForkedFun))
548 newForkEdges[*it].insert(svfForkedFun);
549 }
550 }
551 }
552 }
553 }
554 }
555}
556
561{
564
565 // collect each gep node whose base node has been set as field-insensitive
567 for (auto t: memToFieldsMap)
568 {
569 NodeID base = t.first;
570 const BaseObjVar* obj = pag->getBaseObject(base);
571 assert(obj && "Invalid baseObj in memToFieldsMap");
572 assert(obj->isFieldInsensitive() == obj->isFieldInsensitive());
573 if (obj->isFieldInsensitive())
574 {
575 for (NodeID id : t.second)
576 {
577 if (SVFUtil::isa<GepObjVar>(pag->getSVFVar(id)))
578 {
579 dropNodes.set(id);
580 }
581 else
582 assert(id == base && "Not a GepObj Node or a baseObj Node?");
583 }
584 }
585 }
586
587 // remove the collected redundant gep nodes in each pointers's pts
588 for (SVFIR::iterator nIter = pag->begin(); nIter != pag->end(); ++nIter)
589 {
590 NodeID n = nIter->first;
591
592 const PointsTo &tmpPts = getPts(n);
593 for (NodeID obj : tmpPts)
594 {
595 if (!dropNodes.test(obj))
596 continue;
598 clearPts(n, obj);
599 addPts(n, baseObj);
600 }
601 }
602
603 // clear GepObjVarMap and memToFieldsMap for redundant gepnodes
604 // and remove those nodes from pag
605 for (NodeID n: dropNodes)
606 {
607 NodeID base = pag->getBaseObjVarID(n);
608 GepObjVar *gepNode = SVFUtil::dyn_cast<GepObjVar>(pag->getGNode(n));
609 const APOffset apOffset = gepNode->getConstantFieldIdx();
610 GepObjVarMap.erase(std::make_pair(base, apOffset));
611 memToFieldsMap[base].reset(n);
612
614 }
615}
616
617
625
630{
631
636
639 else
641}
cJSON * p
Definition cJSON.cpp:2559
newitem type
Definition cJSON.cpp:2739
buffer offset
Definition cJSON.cpp:1113
cJSON * n
Definition cJSON.cpp:2558
virtual void normalizePointsTo()
virtual void writeToFile(const std::string &filename)
Interface for analysis result storage on filesystem.
virtual bool readFromFile(const std::string &filename)
virtual void writeObjVarToFile(const std::string &filename)
virtual void clearPts(NodeID id, NodeID element)
Remove element from the points-to set of id.
virtual void readAndSetObjFieldSensitivity(std::ifstream &f, const std::string &delimiterStr)
AliasResult alias(const SVFVar *V1, const SVFVar *V2) override
Interface expose to users of our pointer analysis, given Value infos.
virtual void writeGepObjVarMapToFile(std::fstream &f)
void finalize() override
Finalization of pointer analysis, and normalize points-to information to Bit Vector representation.
virtual void writePtsResultToFile(std::fstream &f)
virtual void expandFIObjs(const PointsTo &pts, PointsTo &expandedPts)
Expand FI objects.
void remapPointsToSets(void)
Remap all points-to sets to use the current mapping.
BVDataPTAImpl(SVFIR *pag, PTATY type, bool alias_check=true)
Constructor.
virtual void onTheFlyThreadCallGraphSolve(const CallSiteToFunPtrMap &callsites, CallEdgeMap &newForkEdges)
On the fly thread call graph construction respecting forksite.
PersistentPointsToCache< PointsTo > ptCache
virtual void onTheFlyCallGraphSolve(const CallSiteToFunPtrMap &callsites, CallEdgeMap &newEdges)
On the fly call graph construction.
virtual bool updateCallGraph(const CallSiteToFunPtrMap &)
Update callgraph. This should be implemented by its subclass.
std::unique_ptr< PTDataTy > ptD
Points-to data.
PersistentPointsToCache< PointsTo > & getPtCache()
PTData< NodeID, NodeSet, NodeID, PointsTo > PTDataTy
void dumpTopLevelPtsTo() override
const PointsTo & getPts(NodeID id) override
virtual void readPtsResultFromFile(std::ifstream &f)
PTDataTy * getPTDataTy() const
Get points-to data structure.
virtual bool addPts(NodeID id, NodeID ptd)
virtual void readGepObjVarMapFromFile(std::ifstream &f)
const SVFVar * getVtablePtr() const
Definition ICFGNode.h:520
bool isVirtualCall() const
Definition ICFGNode.h:510
virtual const FunObjVar * getFunction() const
Get containing function, or null for globals/constants.
iterator begin()
Iterators.
void removeGNode(NodeType *node)
Delete a node.
IDToNodeMapTy::iterator iterator
Node Iterators.
NodeType * getGNode(NodeID id) const
Get a node.
static NodeIDAllocator * get(void)
Return (singleton) allocator.
static const Option< bool > INCDFPTData
Definition Options.h:132
static const OptionMap< PTBackingType > ptDataBacking
PTData type.
Definition Options.h:65
static const Option< u32_t > MaxFieldLimit
Maximum number of field derivations for an object.
Definition Options.h:34
bool isFieldInsensitive(NodeID id) const
virtual void finalize()
Finalization of a pointer analysis, including checking alias correctness.
virtual void dumpPts(NodeID ptr, const PointsTo &pts)
bool print_stat
User input flags.
OrderedMap< const CallICFGNode *, FunctionSet > CallEdgeMap
bool containBlackHoleNode(const PointsTo &pts)
Determine whether a points-to contains a black hole or constant node.
PTAImplTy ptaImplTy
PTA implementation type.
SVFIR * getPAG() const
OrderedNodeSet & getAllValidPtrs()
Get all Valid Pointers for resolution.
virtual void resolveIndCalls(const CallICFGNode *cs, const PointsTo &target, CallEdgeMap &newEdges)
Resolve indirect call edges.
CallGraph * callgraph
Call graph used for pointer analysis.
virtual void resolveCPPIndCalls(const CallICFGNode *cs, const PointsTo &target, CallEdgeMap &newEdges)
Resolve cpp indirect call edges.
void setObjFieldInsensitive(NodeID id)
SVFIR::CallSiteToFunPtrMap CallSiteToFunPtrMap
static SVFIR * pag
SVFIR.
PTATY ptaTy
Pointer analysis Type.
NodeBS toNodeBS() const
Returns this points-to set as a NodeBS.
Definition PointsTo.cpp:313
std::pair< NodeID, APOffset > GepOffset
Definition SVFIR.h:70
NodeID getBaseObjVarID(NodeID id) const
Base and Offset methods for Value and Object node.
Definition SVFIR.h:554
const BaseObjVar * getBaseObject(NodeID id) const
Definition SVFIR.h:498
const ObjVar * getObjVar(NodeID id) const
Definition SVFIR.h:149
NodeBS & getAllFieldsObjVars(const BaseObjVar *obj)
Get all fields of an object.
Definition SVFIR.cpp:573
NodeID addGepObjNode(GepObjVar *gepObj, NodeID base, const APOffset &apOffset)
Definition SVFIR.cpp:547
const CallSiteToFunPtrMap & getIndirectCallsites() const
Add/get indirect callsites.
Definition SVFIR.h:453
const SVFVar * getSVFVar(NodeID id) const
ObjVar/GepObjVar/BaseObjVar.
Definition SVFIR.h:135
OffsetToGepVarMap & getGepObjNodeMap()
Return GepObjVarMap.
Definition SVFIR.h:203
Map< NodeID, NodeBS > MemObjToFieldsMap
Definition SVFIR.h:58
Map< GepOffset, NodeID > OffsetToGepVarMap
Definition SVFIR.h:72
const std::string & getModuleIdentifier() const
Definition SVFIR.h:261
MemObjToFieldsMap & getMemToFieldsMap()
Return memToFieldsMap.
Definition SVFIR.h:198
NodeID getId() const
Get ID.
Definition SVFValue.h:158
std::ostream & outs()
Overwrite llvm::outs()
Definition SVFUtil.h:52
std::vector< std::string > split(const std::string &s, char separator)
Split into two substrings around the first occurrence of a separator string.
Definition SVFUtil.h:196
for isBitcode
Definition BasicTypes.h:70
@ BVDataImpl
Represents BVDataPTAImpl.
Definition PTATY.h:42
PTATY
Pointer analysis type list.
Definition PTATY.h:9
@ Cxt_DDA
context sensitive DDA
Definition PTATY.h:32
@ VFS_WPA
Versioned sparse flow-sensitive WPA.
Definition PTATY.h:21
@ Andersen_WPA
Andersen PTA.
Definition PTATY.h:12
@ FSSPARSE_WPA
Sparse flow sensitive WPA.
Definition PTATY.h:20
@ AndersenSFR_WPA
Stride-based field representation.
Definition PTATY.h:14
@ FieldS_DDA
Field sensitive DDA.
Definition PTATY.h:29
@ FlowS_DDA
Flow sensitive DDA.
Definition PTATY.h:30
@ AndersenWaveDiff_WPA
Diff wave propagation andersen-style WPA.
Definition PTATY.h:15
@ CFLFICI_WPA
Flow-, context-, insensitive CFL-reachability-based analysis.
Definition PTATY.h:23
@ TypeCPP_WPA
Type-based analysis for C++.
Definition PTATY.h:26
@ FSDATAFLOW_WPA
Traditional Dataflow-based flow sensitive WPA.
Definition PTATY.h:19
@ Steensgaard_WPA
Steensgaard PTA.
Definition PTATY.h:16
@ FSCS_WPA
Flow-, context- sensitive WPA.
Definition PTATY.h:22
@ CFLFSCS_WPA
Flow-, context-, CFL-reachability-based analysis.
Definition PTATY.h:25
@ Andersen_BASE
Base Andersen PTA.
Definition PTATY.h:11
@ AndersenSCD_WPA
Selective cycle detection andersen-style WPA.
Definition PTATY.h:13
OrderedSet< NodeID > OrderedNodeSet
Definition GeneralType.h:86
u32_t NodeID
Definition GeneralType.h:76
s64_t APOffset
Definition GeneralType.h:80
AliasResult
Definition SVFType.h:619
@ MayAlias
Definition SVFType.h:621
@ NoAlias
Definition SVFType.h:620
llvm::IRBuilder IRBuilder
Definition BasicTypes.h:76
@ Mutable
Definition PTATY.h:49
@ Persistent
Definition PTATY.h:50