CheckerManager.cpp revision dff6ef903ff4fcb43b5ea292ecd772e381393b5d
1//===--- CheckerManager.cpp - Static Analyzer Checker Manager -------------===// 2// 3// The LLVM Compiler Infrastructure 4// 5// This file is distributed under the University of Illinois Open Source 6// License. See LICENSE.TXT for details. 7// 8//===----------------------------------------------------------------------===// 9// 10// Defines the Static Analyzer Checker Manager. 11// 12//===----------------------------------------------------------------------===// 13 14#include "clang/StaticAnalyzer/Core/CheckerManager.h" 15#include "clang/StaticAnalyzer/Core/Checker.h" 16#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 17#include "clang/StaticAnalyzer/Core/PathSensitive/ObjCMessage.h" 18#include "clang/Analysis/ProgramPoint.h" 19#include "clang/AST/DeclBase.h" 20 21using namespace clang; 22using namespace ento; 23 24bool CheckerManager::hasPathSensitiveCheckers() const { 25 return !StmtCheckers.empty() || 26 !PreObjCMessageCheckers.empty() || 27 !PostObjCMessageCheckers.empty() || 28 !LocationCheckers.empty() || 29 !BindCheckers.empty() || 30 !EndAnalysisCheckers.empty() || 31 !EndPathCheckers.empty() || 32 !BranchConditionCheckers.empty() || 33 !LiveSymbolsCheckers.empty() || 34 !DeadSymbolsCheckers.empty() || 35 !RegionChangesCheckers.empty() || 36 !EvalAssumeCheckers.empty() || 37 !EvalCallCheckers.empty() || 38 !InlineCallCheckers.empty(); 39} 40 41void CheckerManager::finishedCheckerRegistration() { 42#ifndef NDEBUG 43 // Make sure that for every event that has listeners, there is at least 44 // one dispatcher registered for it. 45 for (llvm::DenseMap<EventTag, EventInfo>::iterator 46 I = Events.begin(), E = Events.end(); I != E; ++I) 47 assert(I->second.HasDispatcher && "No dispatcher registered for an event"); 48#endif 49} 50 51//===----------------------------------------------------------------------===// 52// Functions for running checkers for AST traversing.. 53//===----------------------------------------------------------------------===// 54 55void CheckerManager::runCheckersOnASTDecl(const Decl *D, AnalysisManager& mgr, 56 BugReporter &BR) { 57 assert(D); 58 59 unsigned DeclKind = D->getKind(); 60 CachedDeclCheckers *checkers = 0; 61 CachedDeclCheckersMapTy::iterator CCI = CachedDeclCheckersMap.find(DeclKind); 62 if (CCI != CachedDeclCheckersMap.end()) { 63 checkers = &(CCI->second); 64 } else { 65 // Find the checkers that should run for this Decl and cache them. 66 checkers = &CachedDeclCheckersMap[DeclKind]; 67 for (unsigned i = 0, e = DeclCheckers.size(); i != e; ++i) { 68 DeclCheckerInfo &info = DeclCheckers[i]; 69 if (info.IsForDeclFn(D)) 70 checkers->push_back(info.CheckFn); 71 } 72 } 73 74 assert(checkers); 75 for (CachedDeclCheckers::iterator 76 I = checkers->begin(), E = checkers->end(); I != E; ++I) 77 (*I)(D, mgr, BR); 78} 79 80void CheckerManager::runCheckersOnASTBody(const Decl *D, AnalysisManager& mgr, 81 BugReporter &BR) { 82 assert(D && D->hasBody()); 83 84 for (unsigned i = 0, e = BodyCheckers.size(); i != e; ++i) 85 BodyCheckers[i](D, mgr, BR); 86} 87 88//===----------------------------------------------------------------------===// 89// Functions for running checkers for path-sensitive checking. 90//===----------------------------------------------------------------------===// 91 92template <typename CHECK_CTX> 93static void expandGraphWithCheckers(CHECK_CTX checkCtx, 94 ExplodedNodeSet &Dst, 95 const ExplodedNodeSet &Src) { 96 97 typename CHECK_CTX::CheckersTy::const_iterator 98 I = checkCtx.checkers_begin(), E = checkCtx.checkers_end(); 99 if (I == E) { 100 Dst.insert(Src); 101 return; 102 } 103 104 ExplodedNodeSet Tmp1, Tmp2; 105 const ExplodedNodeSet *PrevSet = &Src; 106 107 for (; I != E; ++I) { 108 ExplodedNodeSet *CurrSet = 0; 109 if (I+1 == E) 110 CurrSet = &Dst; 111 else { 112 CurrSet = (PrevSet == &Tmp1) ? &Tmp2 : &Tmp1; 113 CurrSet->clear(); 114 } 115 116 for (ExplodedNodeSet::iterator NI = PrevSet->begin(), NE = PrevSet->end(); 117 NI != NE; ++NI) 118 checkCtx.runChecker(*I, *CurrSet, *NI); 119 120 // Update which NodeSet is the current one. 121 PrevSet = CurrSet; 122 } 123} 124 125namespace { 126 struct CheckStmtContext { 127 typedef SmallVectorImpl<CheckerManager::CheckStmtFunc> CheckersTy; 128 bool IsPreVisit; 129 const CheckersTy &Checkers; 130 const Stmt *S; 131 ExprEngine &Eng; 132 133 CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); } 134 CheckersTy::const_iterator checkers_end() { return Checkers.end(); } 135 136 CheckStmtContext(bool isPreVisit, const CheckersTy &checkers, 137 const Stmt *s, ExprEngine &eng) 138 : IsPreVisit(isPreVisit), Checkers(checkers), S(s), Eng(eng) { } 139 140 void runChecker(CheckerManager::CheckStmtFunc checkFn, 141 ExplodedNodeSet &Dst, ExplodedNode *Pred) { 142 // FIXME: Remove respondsToCallback from CheckerContext; 143 CheckerContext C(Dst, Eng.getBuilder(), Eng, Pred, checkFn.Checker, 144 IsPreVisit ? ProgramPoint::PreStmtKind : 145 ProgramPoint::PostStmtKind, 0, S); 146 checkFn(S, C); 147 } 148 }; 149} 150 151/// \brief Run checkers for visiting Stmts. 152void CheckerManager::runCheckersForStmt(bool isPreVisit, 153 ExplodedNodeSet &Dst, 154 const ExplodedNodeSet &Src, 155 const Stmt *S, 156 ExprEngine &Eng) { 157 CheckStmtContext C(isPreVisit, *getCachedStmtCheckersFor(S, isPreVisit), 158 S, Eng); 159 expandGraphWithCheckers(C, Dst, Src); 160} 161 162namespace { 163 struct CheckObjCMessageContext { 164 typedef std::vector<CheckerManager::CheckObjCMessageFunc> CheckersTy; 165 bool IsPreVisit; 166 const CheckersTy &Checkers; 167 const ObjCMessage &Msg; 168 ExprEngine &Eng; 169 170 CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); } 171 CheckersTy::const_iterator checkers_end() { return Checkers.end(); } 172 173 CheckObjCMessageContext(bool isPreVisit, const CheckersTy &checkers, 174 const ObjCMessage &msg, ExprEngine &eng) 175 : IsPreVisit(isPreVisit), Checkers(checkers), Msg(msg), Eng(eng) { } 176 177 void runChecker(CheckerManager::CheckObjCMessageFunc checkFn, 178 ExplodedNodeSet &Dst, ExplodedNode *Pred) { 179 CheckerContext C(Dst, Eng.getBuilder(), Eng, Pred, checkFn.Checker, 180 IsPreVisit ? ProgramPoint::PreStmtKind : 181 ProgramPoint::PostStmtKind, 0, 182 Msg.getOriginExpr()); 183 checkFn(Msg, C); 184 } 185 }; 186} 187 188/// \brief Run checkers for visiting obj-c messages. 189void CheckerManager::runCheckersForObjCMessage(bool isPreVisit, 190 ExplodedNodeSet &Dst, 191 const ExplodedNodeSet &Src, 192 const ObjCMessage &msg, 193 ExprEngine &Eng) { 194 CheckObjCMessageContext C(isPreVisit, 195 isPreVisit ? PreObjCMessageCheckers 196 : PostObjCMessageCheckers, 197 msg, Eng); 198 expandGraphWithCheckers(C, Dst, Src); 199} 200 201namespace { 202 struct CheckLocationContext { 203 typedef std::vector<CheckerManager::CheckLocationFunc> CheckersTy; 204 const CheckersTy &Checkers; 205 SVal Loc; 206 bool IsLoad; 207 const Stmt *S; 208 ExprEngine &Eng; 209 210 CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); } 211 CheckersTy::const_iterator checkers_end() { return Checkers.end(); } 212 213 CheckLocationContext(const CheckersTy &checkers, 214 SVal loc, bool isLoad, const Stmt *s, ExprEngine &eng) 215 : Checkers(checkers), Loc(loc), IsLoad(isLoad), S(s), Eng(eng) { } 216 217 void runChecker(CheckerManager::CheckLocationFunc checkFn, 218 ExplodedNodeSet &Dst, ExplodedNode *Pred) { 219 CheckerContext C(Dst, Eng.getBuilder(), Eng, Pred, checkFn.Checker, 220 IsLoad ? ProgramPoint::PreLoadKind : 221 ProgramPoint::PreStoreKind, 0, S); 222 checkFn(Loc, IsLoad, C); 223 } 224 }; 225} 226 227/// \brief Run checkers for load/store of a location. 228void CheckerManager::runCheckersForLocation(ExplodedNodeSet &Dst, 229 const ExplodedNodeSet &Src, 230 SVal location, bool isLoad, 231 const Stmt *S, ExprEngine &Eng) { 232 CheckLocationContext C(LocationCheckers, location, isLoad, S, Eng); 233 expandGraphWithCheckers(C, Dst, Src); 234} 235 236namespace { 237 struct CheckBindContext { 238 typedef std::vector<CheckerManager::CheckBindFunc> CheckersTy; 239 const CheckersTy &Checkers; 240 SVal Loc; 241 SVal Val; 242 const Stmt *S; 243 ExprEngine &Eng; 244 245 CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); } 246 CheckersTy::const_iterator checkers_end() { return Checkers.end(); } 247 248 CheckBindContext(const CheckersTy &checkers, 249 SVal loc, SVal val, const Stmt *s, ExprEngine &eng) 250 : Checkers(checkers), Loc(loc), Val(val), S(s), Eng(eng) { } 251 252 void runChecker(CheckerManager::CheckBindFunc checkFn, 253 ExplodedNodeSet &Dst, ExplodedNode *Pred) { 254 CheckerContext C(Dst, Eng.getBuilder(), Eng, Pred, checkFn.Checker, 255 ProgramPoint::PreStmtKind, 0, S); 256 checkFn(Loc, Val, C); 257 } 258 }; 259} 260 261/// \brief Run checkers for binding of a value to a location. 262void CheckerManager::runCheckersForBind(ExplodedNodeSet &Dst, 263 const ExplodedNodeSet &Src, 264 SVal location, SVal val, 265 const Stmt *S, ExprEngine &Eng) { 266 CheckBindContext C(BindCheckers, location, val, S, Eng); 267 expandGraphWithCheckers(C, Dst, Src); 268} 269 270void CheckerManager::runCheckersForEndAnalysis(ExplodedGraph &G, 271 BugReporter &BR, 272 ExprEngine &Eng) { 273 for (unsigned i = 0, e = EndAnalysisCheckers.size(); i != e; ++i) 274 EndAnalysisCheckers[i](G, BR, Eng); 275} 276 277/// \brief Run checkers for end of path. 278void CheckerManager::runCheckersForEndPath(EndOfFunctionNodeBuilder &B, 279 ExprEngine &Eng) { 280 for (unsigned i = 0, e = EndPathCheckers.size(); i != e; ++i) { 281 CheckEndPathFunc fn = EndPathCheckers[i]; 282 EndOfFunctionNodeBuilder specialB = B.withCheckerTag(fn.Checker); 283 fn(specialB, Eng); 284 } 285} 286 287/// \brief Run checkers for branch condition. 288void CheckerManager::runCheckersForBranchCondition(const Stmt *condition, 289 BranchNodeBuilder &B, 290 ExprEngine &Eng) { 291 for (unsigned i = 0, e = BranchConditionCheckers.size(); i != e; ++i) { 292 CheckBranchConditionFunc fn = BranchConditionCheckers[i]; 293 fn(condition, B, Eng); 294 } 295} 296 297/// \brief Run checkers for live symbols. 298void CheckerManager::runCheckersForLiveSymbols(const ProgramState *state, 299 SymbolReaper &SymReaper) { 300 for (unsigned i = 0, e = LiveSymbolsCheckers.size(); i != e; ++i) 301 LiveSymbolsCheckers[i](state, SymReaper); 302} 303 304namespace { 305 struct CheckDeadSymbolsContext { 306 typedef std::vector<CheckerManager::CheckDeadSymbolsFunc> CheckersTy; 307 const CheckersTy &Checkers; 308 SymbolReaper &SR; 309 const Stmt *S; 310 ExprEngine &Eng; 311 312 CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); } 313 CheckersTy::const_iterator checkers_end() { return Checkers.end(); } 314 315 CheckDeadSymbolsContext(const CheckersTy &checkers, SymbolReaper &sr, 316 const Stmt *s, ExprEngine &eng) 317 : Checkers(checkers), SR(sr), S(s), Eng(eng) { } 318 319 void runChecker(CheckerManager::CheckDeadSymbolsFunc checkFn, 320 ExplodedNodeSet &Dst, ExplodedNode *Pred) { 321 CheckerContext C(Dst, Eng.getBuilder(), Eng, Pred, checkFn.Checker, 322 ProgramPoint::PostPurgeDeadSymbolsKind, 0, S); 323 checkFn(SR, C); 324 } 325 }; 326} 327 328/// \brief Run checkers for dead symbols. 329void CheckerManager::runCheckersForDeadSymbols(ExplodedNodeSet &Dst, 330 const ExplodedNodeSet &Src, 331 SymbolReaper &SymReaper, 332 const Stmt *S, 333 ExprEngine &Eng) { 334 CheckDeadSymbolsContext C(DeadSymbolsCheckers, SymReaper, S, Eng); 335 expandGraphWithCheckers(C, Dst, Src); 336} 337 338/// \brief True if at least one checker wants to check region changes. 339bool CheckerManager::wantsRegionChangeUpdate(const ProgramState *state) { 340 for (unsigned i = 0, e = RegionChangesCheckers.size(); i != e; ++i) 341 if (RegionChangesCheckers[i].WantUpdateFn(state)) 342 return true; 343 344 return false; 345} 346 347/// \brief Run checkers for region changes. 348const ProgramState * 349CheckerManager::runCheckersForRegionChanges(const ProgramState *state, 350 const StoreManager::InvalidatedSymbols *invalidated, 351 ArrayRef<const MemRegion *> ExplicitRegions, 352 ArrayRef<const MemRegion *> Regions) { 353 for (unsigned i = 0, e = RegionChangesCheckers.size(); i != e; ++i) { 354 // If any checker declares the state infeasible (or if it starts that way), 355 // bail out. 356 if (!state) 357 return NULL; 358 state = RegionChangesCheckers[i].CheckFn(state, invalidated, 359 ExplicitRegions, Regions); 360 } 361 return state; 362} 363 364/// \brief Run checkers for handling assumptions on symbolic values. 365const ProgramState * 366CheckerManager::runCheckersForEvalAssume(const ProgramState *state, 367 SVal Cond, bool Assumption) { 368 for (unsigned i = 0, e = EvalAssumeCheckers.size(); i != e; ++i) { 369 // If any checker declares the state infeasible (or if it starts that way), 370 // bail out. 371 if (!state) 372 return NULL; 373 state = EvalAssumeCheckers[i](state, Cond, Assumption); 374 } 375 return state; 376} 377 378/// \brief Run checkers for evaluating a call. 379/// Only one checker will evaluate the call. 380void CheckerManager::runCheckersForEvalCall(ExplodedNodeSet &Dst, 381 const ExplodedNodeSet &Src, 382 const CallExpr *CE, 383 ExprEngine &Eng, 384 GraphExpander *defaultEval) { 385 if (EvalCallCheckers.empty() && 386 InlineCallCheckers.empty() && 387 defaultEval == 0) { 388 Dst.insert(Src); 389 return; 390 } 391 392 for (ExplodedNodeSet::iterator 393 NI = Src.begin(), NE = Src.end(); NI != NE; ++NI) { 394 395 ExplodedNode *Pred = *NI; 396 bool anyEvaluated = false; 397 398 // First, check if any of the InlineCall callbacks can evaluate the call. 399 assert(InlineCallCheckers.size() <= 1 && 400 "InlineCall is a special hacky callback to allow intrusive" 401 "evaluation of the call (which simulates inlining). It is " 402 "currently only used by OSAtomicChecker and should go away " 403 "at some point."); 404 for (std::vector<InlineCallFunc>::iterator 405 EI = InlineCallCheckers.begin(), EE = InlineCallCheckers.end(); 406 EI != EE; ++EI) { 407 ExplodedNodeSet checkDst; 408 bool evaluated = (*EI)(CE, Eng, Pred, checkDst); 409 assert(!(evaluated && anyEvaluated) 410 && "There are more than one checkers evaluating the call"); 411 if (evaluated) { 412 anyEvaluated = true; 413 Dst.insert(checkDst); 414#ifdef NDEBUG 415 break; // on release don't check that no other checker also evals. 416#endif 417 } 418 } 419 420#ifdef NDEBUG // on release don't check that no other checker also evals. 421 if (anyEvaluated) { 422 break; 423 } 424#endif 425 426 // Next, check if any of the EvalCall callbacks can evaluate the call. 427 for (std::vector<EvalCallFunc>::iterator 428 EI = EvalCallCheckers.begin(), EE = EvalCallCheckers.end(); 429 EI != EE; ++EI) { 430 ExplodedNodeSet checkDst; 431 CheckerContext C(checkDst, Eng.getBuilder(), Eng, Pred, EI->Checker, 432 ProgramPoint::PostStmtKind, 0, CE); 433 bool evaluated = (*EI)(CE, C); 434 assert(!(evaluated && anyEvaluated) 435 && "There are more than one checkers evaluating the call"); 436 if (evaluated) { 437 anyEvaluated = true; 438 Dst.insert(checkDst); 439#ifdef NDEBUG 440 break; // on release don't check that no other checker also evals. 441#endif 442 } 443 } 444 445 // If none of the checkers evaluated the call, ask ExprEngine to handle it. 446 if (!anyEvaluated) { 447 if (defaultEval) 448 defaultEval->expandGraph(Dst, Pred); 449 else 450 Dst.insert(Pred); 451 } 452 } 453} 454 455/// \brief Run checkers for the entire Translation Unit. 456void CheckerManager::runCheckersOnEndOfTranslationUnit( 457 const TranslationUnitDecl *TU, 458 AnalysisManager &mgr, 459 BugReporter &BR) { 460 for (unsigned i = 0, e = EndOfTranslationUnitCheckers.size(); i != e; ++i) 461 EndOfTranslationUnitCheckers[i](TU, mgr, BR); 462} 463 464void CheckerManager::runCheckersForPrintState(raw_ostream &Out, 465 const ProgramState *State, 466 const char *NL, const char *Sep) { 467 for (llvm::DenseMap<CheckerTag, CheckerRef>::iterator 468 I = CheckerTags.begin(), E = CheckerTags.end(); I != E; ++I) 469 I->second->printState(Out, State, NL, Sep); 470} 471 472//===----------------------------------------------------------------------===// 473// Internal registration functions for AST traversing. 474//===----------------------------------------------------------------------===// 475 476void CheckerManager::_registerForDecl(CheckDeclFunc checkfn, 477 HandlesDeclFunc isForDeclFn) { 478 DeclCheckerInfo info = { checkfn, isForDeclFn }; 479 DeclCheckers.push_back(info); 480} 481 482void CheckerManager::_registerForBody(CheckDeclFunc checkfn) { 483 BodyCheckers.push_back(checkfn); 484} 485 486//===----------------------------------------------------------------------===// 487// Internal registration functions for path-sensitive checking. 488//===----------------------------------------------------------------------===// 489 490void CheckerManager::_registerForPreStmt(CheckStmtFunc checkfn, 491 HandlesStmtFunc isForStmtFn) { 492 StmtCheckerInfo info = { checkfn, isForStmtFn, /*IsPreVisit*/true }; 493 StmtCheckers.push_back(info); 494} 495void CheckerManager::_registerForPostStmt(CheckStmtFunc checkfn, 496 HandlesStmtFunc isForStmtFn) { 497 StmtCheckerInfo info = { checkfn, isForStmtFn, /*IsPreVisit*/false }; 498 StmtCheckers.push_back(info); 499} 500 501void CheckerManager::_registerForPreObjCMessage(CheckObjCMessageFunc checkfn) { 502 PreObjCMessageCheckers.push_back(checkfn); 503} 504void CheckerManager::_registerForPostObjCMessage(CheckObjCMessageFunc checkfn) { 505 PostObjCMessageCheckers.push_back(checkfn); 506} 507 508void CheckerManager::_registerForLocation(CheckLocationFunc checkfn) { 509 LocationCheckers.push_back(checkfn); 510} 511 512void CheckerManager::_registerForBind(CheckBindFunc checkfn) { 513 BindCheckers.push_back(checkfn); 514} 515 516void CheckerManager::_registerForEndAnalysis(CheckEndAnalysisFunc checkfn) { 517 EndAnalysisCheckers.push_back(checkfn); 518} 519 520void CheckerManager::_registerForEndPath(CheckEndPathFunc checkfn) { 521 EndPathCheckers.push_back(checkfn); 522} 523 524void CheckerManager::_registerForBranchCondition( 525 CheckBranchConditionFunc checkfn) { 526 BranchConditionCheckers.push_back(checkfn); 527} 528 529void CheckerManager::_registerForLiveSymbols(CheckLiveSymbolsFunc checkfn) { 530 LiveSymbolsCheckers.push_back(checkfn); 531} 532 533void CheckerManager::_registerForDeadSymbols(CheckDeadSymbolsFunc checkfn) { 534 DeadSymbolsCheckers.push_back(checkfn); 535} 536 537void CheckerManager::_registerForRegionChanges(CheckRegionChangesFunc checkfn, 538 WantsRegionChangeUpdateFunc wantUpdateFn) { 539 RegionChangesCheckerInfo info = {checkfn, wantUpdateFn}; 540 RegionChangesCheckers.push_back(info); 541} 542 543void CheckerManager::_registerForEvalAssume(EvalAssumeFunc checkfn) { 544 EvalAssumeCheckers.push_back(checkfn); 545} 546 547void CheckerManager::_registerForEvalCall(EvalCallFunc checkfn) { 548 EvalCallCheckers.push_back(checkfn); 549} 550 551void CheckerManager::_registerForInlineCall(InlineCallFunc checkfn) { 552 InlineCallCheckers.push_back(checkfn); 553} 554 555void CheckerManager::_registerForEndOfTranslationUnit( 556 CheckEndOfTranslationUnit checkfn) { 557 EndOfTranslationUnitCheckers.push_back(checkfn); 558} 559 560//===----------------------------------------------------------------------===// 561// Implementation details. 562//===----------------------------------------------------------------------===// 563 564CheckerManager::CachedStmtCheckers * 565CheckerManager::getCachedStmtCheckersFor(const Stmt *S, bool isPreVisit) { 566 assert(S); 567 568 CachedStmtCheckersKey key(S->getStmtClass(), isPreVisit); 569 CachedStmtCheckers *checkers = 0; 570 CachedStmtCheckersMapTy::iterator CCI = CachedStmtCheckersMap.find(key); 571 if (CCI != CachedStmtCheckersMap.end()) { 572 checkers = &(CCI->second); 573 } else { 574 // Find the checkers that should run for this Stmt and cache them. 575 checkers = &CachedStmtCheckersMap[key]; 576 for (unsigned i = 0, e = StmtCheckers.size(); i != e; ++i) { 577 StmtCheckerInfo &info = StmtCheckers[i]; 578 if (info.IsPreVisit == isPreVisit && info.IsForStmtFn(S)) 579 checkers->push_back(info.CheckFn); 580 } 581 } 582 583 assert(checkers); 584 return checkers; 585} 586 587CheckerManager::~CheckerManager() { 588 for (unsigned i = 0, e = CheckerDtors.size(); i != e; ++i) 589 CheckerDtors[i](); 590} 591 592// Anchor for the vtable. 593GraphExpander::~GraphExpander() { } 594