RecyclerChecker.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  4. //-------------------------------------------------------------------------------------------------------
  5. #include "RecyclerChecker.h"
  6. MainVisitor::MainVisitor(
  7. CompilerInstance& compilerInstance, ASTContext& context, bool fix)
  8. : _compilerInstance(compilerInstance), _context(context),
  9. _fix(fix), _fixed(false), _diagEngine(context.getDiagnostics()),
  10. _barrierTypeDefined(false)
  11. {
  12. if (_fix)
  13. {
  14. _rewriter.setSourceMgr(compilerInstance.getSourceManager(),
  15. compilerInstance.getLangOpts());
  16. }
  17. #define SWB_WIKI \
  18. "https://github.com/microsoft/ChakraCore/wiki/Software-Write-Barrier#coding-rules"
  19. _diagUnbarrieredField = _diagEngine.getCustomDiagID(
  20. DiagnosticsEngine::Error,
  21. "Unbarriered field, see " SWB_WIKI);
  22. _diagIllegalBarrierCast = _diagEngine.getCustomDiagID(
  23. DiagnosticsEngine::Error,
  24. "Illegal casting away of write barrier, see " SWB_WIKI);
  25. #undef SWB_WIKI
  26. }
  27. void MainVisitor::ReportUnbarriedField(SourceLocation location)
  28. {
  29. DiagReport(location, _diagUnbarrieredField);
  30. }
  31. void MainVisitor::ReportIllegalBarrierCast(SourceLocation location)
  32. {
  33. DiagReport(location, _diagIllegalBarrierCast);
  34. }
  35. void MainVisitor::DiagReport(SourceLocation location, unsigned diagId)
  36. {
  37. _diagEngine.Report(location, diagId);
  38. }
  39. bool MainVisitor::VisitCXXRecordDecl(CXXRecordDecl* recordDecl)
  40. {
  41. if (Log::GetLevel() < Log::LogLevel::Verbose)
  42. {
  43. return true; // At least Verbose level, otherwise this not needed
  44. }
  45. std::string typeName = recordDecl->getQualifiedNameAsString();
  46. // Ignore (system/non-GC types) before seeing "Memory::NoWriteBarrierField"
  47. if (!_barrierTypeDefined)
  48. {
  49. if (typeName != "Memory::NoWriteBarrierField")
  50. {
  51. return true;
  52. }
  53. _barrierTypeDefined = true;
  54. }
  55. if (!recordDecl->hasDefinition())
  56. {
  57. return true;
  58. }
  59. bool hasUnbarrieredPointer = false;
  60. bool hasBarrieredField = false;
  61. for (auto field : recordDecl->fields())
  62. {
  63. const QualType qualType = field->getType();
  64. const Type* type = qualType.getTypePtr();
  65. auto fieldTypeName = qualType.getAsString();
  66. if (StartsWith(fieldTypeName, "typename WriteBarrierFieldTypeTraits") ||
  67. StartsWith(fieldTypeName, "const typename WriteBarrierFieldTypeTraits"))
  68. {
  69. // Note this only indicates the class is write-barrier annotated
  70. hasBarrieredField = true;
  71. }
  72. else if (type->isPointerType())
  73. {
  74. hasUnbarrieredPointer = true;
  75. }
  76. else if (type->isCompoundType())
  77. {
  78. // If the field is a compound type,
  79. // check if it is a fully barriered type or
  80. // has unprotected pointer fields
  81. if (Contains(_pointerClasses, fieldTypeName))
  82. {
  83. hasUnbarrieredPointer = true;
  84. }
  85. else if (Contains(_barrieredClasses, fieldTypeName))
  86. {
  87. hasBarrieredField = true;
  88. }
  89. }
  90. }
  91. if (hasUnbarrieredPointer)
  92. {
  93. _pointerClasses.insert(typeName);
  94. }
  95. else if (hasBarrieredField)
  96. {
  97. _barrieredClasses.insert(typeName);
  98. }
  99. return true;
  100. }
  101. template <class PushFieldType>
  102. void MainVisitor::ProcessUnbarrieredFields(
  103. CXXRecordDecl* recordDecl, const PushFieldType& pushFieldType)
  104. {
  105. std::string typeName = recordDecl->getQualifiedNameAsString();
  106. if (typeName == "Memory::WriteBarrierPtr")
  107. {
  108. return; // Skip WriteBarrierPtr itself
  109. }
  110. const auto& sourceMgr = _compilerInstance.getSourceManager();
  111. for (auto field : recordDecl->fields())
  112. {
  113. const QualType qualType = field->getType();
  114. string fieldTypeName = qualType.getAsString();
  115. string fieldName = field->getNameAsString();
  116. if (StartsWith(fieldTypeName, "WriteBarrierPtr<") || // WriteBarrierPtr fields
  117. Contains(fieldTypeName, "_no_write_barrier_policy, ")) // FieldNoBarrier
  118. {
  119. continue; // skip
  120. }
  121. // If an annotated field type is struct/class/union (RecordType), the
  122. // field type in turn should likely be annoatated.
  123. if (fieldTypeName.back() != '*' // not "... *"
  124. &&
  125. (
  126. StartsWith(fieldTypeName, "typename WriteBarrierFieldTypeTraits") ||
  127. StartsWith(fieldTypeName, "WriteBarrierFieldTypeTraits") ||
  128. StartsWith(fieldTypeName, "const typename WriteBarrierFieldTypeTraits") ||
  129. StartsWith(fieldTypeName, "const WriteBarrierFieldTypeTraits") ||
  130. fieldName.length() == 0 // anonymous union/struct
  131. ))
  132. {
  133. auto originalType = qualType->getUnqualifiedDesugaredType();
  134. if (auto arrayType = dyn_cast<ArrayType>(originalType))
  135. {
  136. originalType = arrayType->getElementType()->getUnqualifiedDesugaredType();
  137. }
  138. string originalTypeName = QualType(originalType, 0).getAsString();
  139. if (isa<RecordType>(originalType) &&
  140. !StartsWith(originalTypeName, "class Memory::WriteBarrierPtr<"))
  141. {
  142. if (pushFieldType(originalType))
  143. {
  144. Log::verbose() << "Queue field type: " << originalTypeName
  145. << " (" << typeName << "::" << fieldName << ")\n";
  146. }
  147. }
  148. }
  149. else
  150. {
  151. SourceLocation location = field->getLocStart();
  152. if (this->_fix)
  153. {
  154. const char* begin = sourceMgr.getCharacterData(location);
  155. const char* end = begin;
  156. if (MatchType(fieldTypeName, begin, &end))
  157. {
  158. _rewriter.ReplaceText(
  159. location, end - begin,
  160. GetFieldTypeAnnotation(qualType) + string(begin, end) +
  161. (*end == ' ' ? ")" : ") "));
  162. _fixed = true;
  163. continue;
  164. }
  165. Log::errs() << "Fail to fix: " << fieldTypeName << " "
  166. << fieldName << "\n";
  167. }
  168. ReportUnbarriedField(location);
  169. }
  170. }
  171. }
  172. static bool SkipSpace(const char*& p)
  173. {
  174. if (*p == ' ')
  175. {
  176. ++p;
  177. return true;
  178. }
  179. return false;
  180. }
  181. template <size_t N>
  182. static bool SkipPrefix(const char*& p, const char (&prefix)[N])
  183. {
  184. if (StartsWith(p, prefix))
  185. {
  186. p += N - 1; // skip
  187. return true;
  188. }
  189. return false;
  190. }
  191. static bool SkipPrefix(const char*& p, const string& prefix)
  192. {
  193. if (StartsWith(p, prefix))
  194. {
  195. p += prefix.length(); // skip
  196. return true;
  197. }
  198. return false;
  199. }
  200. static bool SkipTemplateParameters(const char*& p)
  201. {
  202. if (*p == '<')
  203. {
  204. ++p;
  205. int left = 1;
  206. while (left && *p)
  207. {
  208. switch (*p++)
  209. {
  210. case '<': ++left; break;
  211. case '>': --left; break;
  212. }
  213. }
  214. return true;
  215. }
  216. return false;
  217. }
  218. bool MainVisitor::MatchType(const string& type, const char* source, const char** pSourceEnd)
  219. {
  220. // try match type in source directly (clang "bool" type is "_Bool")
  221. if (SkipPrefix(source, type) || (type == "_Bool" && SkipPrefix(source, "bool")))
  222. {
  223. *pSourceEnd = source;
  224. return true;
  225. }
  226. const char* p = type.c_str();
  227. while (*p && *source)
  228. {
  229. if (SkipSpace(p) || SkipSpace(source))
  230. {
  231. continue;
  232. }
  233. #define SKIP_EITHER_PREFIX(prefix) \
  234. (SkipPrefix(p, prefix) || SkipPrefix(source, prefix))
  235. if (SKIP_EITHER_PREFIX("const ") ||
  236. SKIP_EITHER_PREFIX("class ") ||
  237. SKIP_EITHER_PREFIX("struct ") ||
  238. SKIP_EITHER_PREFIX("union ") ||
  239. SKIP_EITHER_PREFIX("enum "))
  240. {
  241. continue;
  242. }
  243. #undef SKIP_EITHER_PREFIX
  244. // type may contain [...] array specifier, while source has it after field name
  245. if (*p == '[')
  246. {
  247. while (*p && *p++ != ']');
  248. continue;
  249. }
  250. // skip <...> in both
  251. if (SkipTemplateParameters(p) || SkipTemplateParameters(source))
  252. {
  253. continue;
  254. }
  255. // type may contain fully qualified name but source may or may not
  256. const char* pSkipScopeType = strstr(p, "::");
  257. if (pSkipScopeType && !memchr(p, ' ', pSkipScopeType - p))
  258. {
  259. pSkipScopeType += 2;
  260. if (strncmp(source, p, pSkipScopeType - p) == 0)
  261. {
  262. source += pSkipScopeType - p;
  263. }
  264. p = pSkipScopeType;
  265. continue;
  266. }
  267. if (*p == *source)
  268. {
  269. while (*p && *source && *p == *source && !strchr("<>", *p))
  270. {
  271. ++p, ++source;
  272. }
  273. continue;
  274. }
  275. if (*p != *source)
  276. {
  277. return false; // mismatch
  278. }
  279. }
  280. if (!*p && *source) // type match completed and having remaining source
  281. {
  282. while (*(source - 1) == ' ') --source; // try to stop after a non-space char
  283. *pSourceEnd = source;
  284. return true;
  285. }
  286. return false;
  287. }
  288. const char* MainVisitor::GetFieldTypeAnnotation(QualType qtype)
  289. {
  290. if (qtype->isPointerType())
  291. {
  292. auto type = qtype->getUnqualifiedDesugaredType()->getPointeeType().getTypePtr();
  293. const auto& i = _allocationTypes.find(type);
  294. if (i != _allocationTypes.end()
  295. && i->second == AllocationTypes::NonRecycler)
  296. {
  297. return "FieldNoBarrier(";
  298. }
  299. }
  300. return "Field(";
  301. }
  302. bool MainVisitor::VisitFunctionDecl(FunctionDecl* functionDecl)
  303. {
  304. if (functionDecl->hasBody())
  305. {
  306. CheckAllocationsInFunctionVisitor visitor(this, functionDecl);
  307. visitor.TraverseDecl(functionDecl);
  308. }
  309. return true;
  310. }
  311. void MainVisitor::RecordAllocation(QualType qtype, AllocationTypes allocationType)
  312. {
  313. auto type = qtype->getCanonicalTypeInternal().getTypePtr();
  314. _allocationTypes[type] |= allocationType;
  315. }
  316. void MainVisitor::RecordRecyclerAllocation(const string& allocationFunction, const string& type)
  317. {
  318. _allocatorTypeMap[allocationFunction].insert(type);
  319. }
  320. template <class Set, class DumpItemFunc>
  321. void MainVisitor::dump(const char* name, const Set& set, const DumpItemFunc& func)
  322. {
  323. Log::verbose() << "-------------------------\n\n";
  324. Log::verbose() << name << "\n";
  325. Log::verbose() << "-------------------------\n\n";
  326. for (auto item : set)
  327. {
  328. func(Log::verbose(), item);
  329. }
  330. Log::verbose() << "-------------------------\n\n";
  331. }
  332. template <class Item>
  333. void MainVisitor::dump(const char* name, const set<Item>& set)
  334. {
  335. dump(name, set, [](raw_ostream& out, const Item& item)
  336. {
  337. out << " " << item << "\n";
  338. });
  339. }
  340. void MainVisitor::dump(const char* name, const unordered_set<const Type*> set)
  341. {
  342. dump(name, set, [&](raw_ostream& out, const Type* type)
  343. {
  344. out << " " << QualType(type, 0).getAsString() << "\n";
  345. });
  346. }
  347. void MainVisitor::Inspect()
  348. {
  349. #define Dump(coll) dump(#coll, _##coll)
  350. Dump(pointerClasses);
  351. Dump(barrieredClasses);
  352. Log::verbose() << "Recycler allocations\n";
  353. for (auto item : _allocatorTypeMap)
  354. {
  355. dump(item.first.c_str(), item.second);
  356. }
  357. std::queue<const Type*> queue; // queue of types to check
  358. std::unordered_set<const Type*> barrierTypes; // set of types queued
  359. auto pushBarrierType = [&](const Type* type) -> bool
  360. {
  361. if (barrierTypes.insert(type).second)
  362. {
  363. queue.push(type);
  364. return true;
  365. }
  366. return false;
  367. };
  368. for (auto item : _allocationTypes)
  369. {
  370. if (item.second & AllocationTypes::WriteBarrier)
  371. {
  372. pushBarrierType(item.first);
  373. }
  374. }
  375. dump("WriteBarrier allocation types", barrierTypes);
  376. // Examine all barrierd types. They should be fully wb annotated.
  377. while (!queue.empty())
  378. {
  379. auto type = queue.front();
  380. queue.pop();
  381. auto r = type->getCanonicalTypeInternal()->getAsCXXRecordDecl();
  382. if (r)
  383. {
  384. auto typeName = r->getQualifiedNameAsString();
  385. ProcessUnbarrieredFields(r, pushBarrierType);
  386. // queue the type's base classes
  387. for (const auto& base: r->bases())
  388. {
  389. if (pushBarrierType(base.getType().getTypePtr()))
  390. {
  391. Log::verbose() << "Queue base type: " << base.getType().getAsString()
  392. << " (base of " << typeName << ")\n";
  393. }
  394. }
  395. }
  396. }
  397. #undef Dump
  398. }
  399. bool MainVisitor::ApplyFix()
  400. {
  401. return _fixed ? _rewriter.overwriteChangedFiles() : false;
  402. }
  403. static AllocationTypes CheckAllocationType(const CXXStaticCastExpr* castNode)
  404. {
  405. QualType targetType = castNode->getTypeAsWritten();
  406. if (const IdentifierInfo* info = targetType.getBaseTypeIdentifier())
  407. {
  408. return info->getName().equals("Recycler") ?
  409. AllocationTypes::Recycler : AllocationTypes::NonRecycler;
  410. }
  411. else
  412. {
  413. // Unknown template dependent allocator types
  414. return AllocationTypes::Unknown;
  415. }
  416. }
  417. template <class A0, class A1, class T>
  418. void CheckAllocationsInFunctionVisitor::VisitAllocate(
  419. const A0& getArg0, const A1& getArg1, const T& getAllocType)
  420. {
  421. const Expr* firstArgNode = getArg0();
  422. // Check if the first argument (to new or AllocateArray) is a static cast
  423. // AllocatorNew/AllocateArray in Chakra always does a static_cast to the AllocatorType
  424. const CXXStaticCastExpr* castNode = nullptr;
  425. if (firstArgNode != nullptr &&
  426. (castNode = dyn_cast<CXXStaticCastExpr>(firstArgNode)))
  427. {
  428. QualType allocatedType = getAllocType();
  429. string allocatedTypeStr = allocatedType.getAsString();
  430. auto allocationType = CheckAllocationType(castNode);
  431. if (allocationType == AllocationTypes::Recycler) // Recycler allocation
  432. {
  433. const Expr* secondArgNode = getArg1();
  434. // Chakra has two types of allocating functions- throwing and non-throwing
  435. // However, recycler allocations are always throwing, so the second parameter
  436. // should be the address of the allocator function
  437. auto unaryNode = cast<UnaryOperator>(secondArgNode);
  438. if (unaryNode != nullptr && unaryNode->getOpcode() == UnaryOperatorKind::UO_AddrOf)
  439. {
  440. Expr* subExpr = unaryNode->getSubExpr();
  441. if (DeclRefExpr* declRef = cast<DeclRefExpr>(subExpr))
  442. {
  443. auto declNameInfo = declRef->getNameInfo();
  444. auto allocationFunctionStr = declNameInfo.getName().getAsString();
  445. _mainVisitor->RecordRecyclerAllocation(allocationFunctionStr, allocatedTypeStr);
  446. if (!Contains(allocationFunctionStr, "Leaf"))
  447. {
  448. // Recycler write barrier allocation -- unless "Leaf" in allocFunc
  449. allocationType = AllocationTypes::RecyclerWriteBarrier;
  450. }
  451. }
  452. else
  453. {
  454. Log::errs() << "ERROR: (internal) Expected DeclRefExpr:\n";
  455. subExpr->dump();
  456. }
  457. }
  458. else if (auto mExpr = cast<MaterializeTemporaryExpr>(secondArgNode))
  459. {
  460. auto name = mExpr->GetTemporaryExpr()->IgnoreImpCasts()->getType().getAsString();
  461. if (StartsWith(name, "InfoBitsWrapper<")) // && Contains(name, "WithBarrierBit"))
  462. {
  463. // RecyclerNewEnumClass, RecyclerNewWithInfoBits -- always have WithBarrier varients
  464. allocationType = AllocationTypes::RecyclerWriteBarrier;
  465. }
  466. }
  467. else
  468. {
  469. Log::errs() << "ERROR: (internal) Expected unary node or MaterializeTemporaryExpr:\n";
  470. secondArgNode->dump();
  471. }
  472. }
  473. if (allocationType & AllocationTypes::WriteBarrier)
  474. {
  475. Log::verbose() << "In \"" << _functionDecl->getQualifiedNameAsString() << "\"\n";
  476. Log::verbose() << " Allocating \"" << allocatedTypeStr << "\" in write barriered memory\n";
  477. }
  478. _mainVisitor->RecordAllocation(allocatedType, allocationType);
  479. }
  480. }
  481. bool CheckAllocationsInFunctionVisitor::VisitCXXNewExpr(CXXNewExpr* newExpr)
  482. {
  483. if (newExpr->getNumPlacementArgs() > 1)
  484. {
  485. VisitAllocate(
  486. [=]() { return newExpr->getPlacementArg(0); },
  487. [=]() { return newExpr->getPlacementArg(1); },
  488. [=]() { return newExpr->getAllocatedType(); }
  489. );
  490. }
  491. return true;
  492. }
  493. bool CheckAllocationsInFunctionVisitor::VisitCallExpr(CallExpr* callExpr)
  494. {
  495. // Check callExpr for AllocateArray
  496. auto callee = callExpr->getDirectCallee();
  497. if (callExpr->getNumArgs() == 3 &&
  498. callee &&
  499. callee->getName().equals("AllocateArray"))
  500. {
  501. VisitAllocate(
  502. [=]() { return callExpr->getArg(0); },
  503. [=]() { return callExpr->getArg(1); },
  504. [=]()
  505. {
  506. auto retType = callExpr->getCallReturnType(_mainVisitor->getContext());
  507. return QualType(retType->getAs<PointerType>()->getPointeeType());
  508. }
  509. );
  510. }
  511. return true;
  512. }
  513. // Check if type is a "Field() *" pointer type, or alternatively a pointer to
  514. // any type in "alt" if provided.
  515. bool CheckAllocationsInFunctionVisitor::IsFieldPointer(
  516. const QualType& qtype, const char* alt)
  517. {
  518. if (qtype->isPointerType())
  519. {
  520. auto name = qtype->getPointeeType()
  521. .getDesugaredType(_mainVisitor->getContext()).getAsString();
  522. return StartsWith(name, "class Memory::WriteBarrierPtr<")
  523. || StartsWith(name, "typename WriteBarrierFieldTypeTraits<")
  524. || (alt && strstr(alt, name.c_str()));
  525. }
  526. return false;
  527. }
  528. bool CheckAllocationsInFunctionVisitor::CommonVisitCastExpr(CastExpr *cast)
  529. {
  530. if (IsFieldPointer(cast->getSubExpr()->getType()) && // from Field() *
  531. cast->getType()->isPointerType() && // to a pointer type
  532. !IsFieldPointer(cast->getType(), // not another Field() *
  533. "int|float|double|unsigned char")) // not int/float/double/byte *
  534. {
  535. _mainVisitor->ReportIllegalBarrierCast(cast->getLocStart());
  536. if (Log::GetLevel() >= Log::LogLevel::Info)
  537. {
  538. cast->dumpColor();
  539. cast->getSubExpr()->getType()->getPointeeType()
  540. .getDesugaredType(_mainVisitor->getContext()).dump("CAST_FROM");
  541. cast->getType()->getPointeeType()
  542. .getDesugaredType(_mainVisitor->getContext()).dump("CAST_TO");
  543. }
  544. }
  545. return true;
  546. }
  547. void RecyclerCheckerConsumer::HandleTranslationUnit(ASTContext& context)
  548. {
  549. MainVisitor mainVisitor(_compilerInstance, context, _fix);
  550. mainVisitor.TraverseDecl(context.getTranslationUnitDecl());
  551. mainVisitor.Inspect();
  552. mainVisitor.ApplyFix();
  553. }
  554. std::unique_ptr<ASTConsumer> RecyclerCheckerAction::CreateASTConsumer(
  555. CompilerInstance& compilerInstance, llvm::StringRef)
  556. {
  557. return llvm::make_unique<RecyclerCheckerConsumer>(compilerInstance, _fix);
  558. }
  559. bool RecyclerCheckerAction::ParseArgs(
  560. const CompilerInstance& compilerInstance, const std::vector<std::string>& args)
  561. {
  562. for (auto i = args.begin(); i != args.end(); i++)
  563. {
  564. if (*i == "-fix")
  565. {
  566. this->_fix = true;
  567. }
  568. else if (*i == "-info")
  569. {
  570. Log::SetLevel(Log::LogLevel::Info);
  571. }
  572. else if (*i == "-verbose")
  573. {
  574. Log::SetLevel(Log::LogLevel::Verbose);
  575. }
  576. else
  577. {
  578. Log::errs()
  579. << "ERROR: Unrecognized check-recycler option: " << *i << "\n"
  580. << "Supported options:\n"
  581. << " -fix Fix missing write barrier annotations"
  582. << " -info Log info messages\n"
  583. << " -verbose Log verbose messages\n";
  584. return false;
  585. }
  586. }
  587. return true;
  588. }
  589. static FrontendPluginRegistry::Add<RecyclerCheckerAction> recyclerPlugin(
  590. "check-recycler", "Checks the recycler allocations");