| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677 |
- //-------------------------------------------------------------------------------------------------------
- // Copyright (C) Microsoft. All rights reserved.
- // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
- //-------------------------------------------------------------------------------------------------------
- #include "RecyclerChecker.h"
- MainVisitor::MainVisitor(
- CompilerInstance& compilerInstance, ASTContext& context, bool fix)
- : _compilerInstance(compilerInstance), _context(context),
- _fix(fix), _fixed(false), _diagEngine(context.getDiagnostics()),
- _barrierTypeDefined(false)
- {
- if (_fix)
- {
- _rewriter.setSourceMgr(compilerInstance.getSourceManager(),
- compilerInstance.getLangOpts());
- }
- #define SWB_WIKI \
- "https://github.com/microsoft/ChakraCore/wiki/Software-Write-Barrier#coding-rules"
- _diagUnbarrieredField = _diagEngine.getCustomDiagID(
- DiagnosticsEngine::Error,
- "Unbarriered field, see " SWB_WIKI);
- _diagIllegalBarrierCast = _diagEngine.getCustomDiagID(
- DiagnosticsEngine::Error,
- "Illegal casting away of write barrier, see " SWB_WIKI);
- #undef SWB_WIKI
- }
- void MainVisitor::ReportUnbarriedField(SourceLocation location)
- {
- DiagReport(location, _diagUnbarrieredField);
- }
- void MainVisitor::ReportIllegalBarrierCast(SourceLocation location)
- {
- DiagReport(location, _diagIllegalBarrierCast);
- }
- void MainVisitor::DiagReport(SourceLocation location, unsigned diagId)
- {
- _diagEngine.Report(location, diagId);
- }
- bool MainVisitor::VisitCXXRecordDecl(CXXRecordDecl* recordDecl)
- {
- if (Log::GetLevel() < Log::LogLevel::Verbose)
- {
- return true; // At least Verbose level, otherwise this not needed
- }
- std::string typeName = recordDecl->getQualifiedNameAsString();
- // Ignore (system/non-GC types) before seeing "Memory::NoWriteBarrierField"
- if (!_barrierTypeDefined)
- {
- if (typeName != "Memory::NoWriteBarrierField")
- {
- return true;
- }
- _barrierTypeDefined = true;
- }
- if (!recordDecl->hasDefinition())
- {
- return true;
- }
- bool hasUnbarrieredPointer = false;
- bool hasBarrieredField = false;
- for (auto field : recordDecl->fields())
- {
- const QualType qualType = field->getType();
- const Type* type = qualType.getTypePtr();
- auto fieldTypeName = qualType.getAsString();
- if (StartsWith(fieldTypeName, "typename WriteBarrierFieldTypeTraits") ||
- StartsWith(fieldTypeName, "const typename WriteBarrierFieldTypeTraits"))
- {
- // Note this only indicates the class is write-barrier annotated
- hasBarrieredField = true;
- }
- else if (type->isPointerType())
- {
- hasUnbarrieredPointer = true;
- }
- else if (type->isCompoundType())
- {
- // If the field is a compound type,
- // check if it is a fully barriered type or
- // has unprotected pointer fields
- if (Contains(_pointerClasses, fieldTypeName))
- {
- hasUnbarrieredPointer = true;
- }
- else if (Contains(_barrieredClasses, fieldTypeName))
- {
- hasBarrieredField = true;
- }
- }
- }
- if (hasUnbarrieredPointer)
- {
- _pointerClasses.insert(typeName);
- }
- else if (hasBarrieredField)
- {
- _barrieredClasses.insert(typeName);
- }
- return true;
- }
- template <class PushFieldType>
- void MainVisitor::ProcessUnbarrieredFields(
- CXXRecordDecl* recordDecl, const PushFieldType& pushFieldType)
- {
- std::string typeName = recordDecl->getQualifiedNameAsString();
- if (typeName == "Memory::WriteBarrierPtr")
- {
- return; // Skip WriteBarrierPtr itself
- }
- const auto& sourceMgr = _compilerInstance.getSourceManager();
- for (auto field : recordDecl->fields())
- {
- const QualType qualType = field->getType();
- string fieldTypeName = qualType.getAsString();
- string fieldName = field->getNameAsString();
- if (StartsWith(fieldTypeName, "WriteBarrierPtr<") || // WriteBarrierPtr fields
- Contains(fieldTypeName, "_no_write_barrier_policy, ")) // FieldNoBarrier
- {
- continue; // skip
- }
- // If an annotated field type is struct/class/union (RecordType), the
- // field type in turn should likely be annotated.
- if (fieldTypeName.back() != '*' // not "... *"
- &&
- (
- StartsWith(fieldTypeName, "typename WriteBarrierFieldTypeTraits") ||
- StartsWith(fieldTypeName, "WriteBarrierFieldTypeTraits") ||
- StartsWith(fieldTypeName, "const typename WriteBarrierFieldTypeTraits") ||
- StartsWith(fieldTypeName, "const WriteBarrierFieldTypeTraits") ||
- fieldName.length() == 0 // anonymous union/struct
- ))
- {
- auto originalType = qualType->getUnqualifiedDesugaredType();
- if (auto arrayType = dyn_cast<ArrayType>(originalType))
- {
- originalType = arrayType->getElementType()->getUnqualifiedDesugaredType();
- }
- string originalTypeName = QualType(originalType, 0).getAsString();
- if (isa<RecordType>(originalType) &&
- !StartsWith(originalTypeName, "class Memory::WriteBarrierPtr<"))
- {
- if (pushFieldType(originalType))
- {
- Log::verbose() << "Queue field type: " << originalTypeName
- << " (" << typeName << "::" << fieldName << ")\n";
- }
- }
- }
- else
- {
- SourceLocation location = field->getLocStart();
- if (this->_fix)
- {
- const char* begin = sourceMgr.getCharacterData(location);
- const char* end = begin;
- if (MatchType(fieldTypeName, begin, &end))
- {
- _rewriter.ReplaceText(
- location, end - begin,
- GetFieldTypeAnnotation(qualType) + string(begin, end) +
- (*end == ' ' ? ")" : ") "));
- _fixed = true;
- continue;
- }
- Log::errs() << "Fail to fix: " << fieldTypeName << " "
- << fieldName << "\n";
- }
- ReportUnbarriedField(location);
- }
- }
- }
- static bool SkipSpace(const char*& p)
- {
- if (*p == ' ')
- {
- ++p;
- return true;
- }
- return false;
- }
- template <size_t N>
- static bool SkipPrefix(const char*& p, const char (&prefix)[N])
- {
- if (StartsWith(p, prefix))
- {
- p += N - 1; // skip
- return true;
- }
- return false;
- }
- static bool SkipPrefix(const char*& p, const string& prefix)
- {
- if (StartsWith(p, prefix))
- {
- p += prefix.length(); // skip
- return true;
- }
- return false;
- }
- static bool SkipTemplateParameters(const char*& p)
- {
- if (*p == '<')
- {
- ++p;
- int left = 1;
- while (left && *p)
- {
- switch (*p++)
- {
- case '<': ++left; break;
- case '>': --left; break;
- }
- }
- return true;
- }
- return false;
- }
- bool MainVisitor::MatchType(const string& type, const char* source, const char** pSourceEnd)
- {
- // try match type in source directly (clang "bool" type is "_Bool")
- if (SkipPrefix(source, type) || (type == "_Bool" && SkipPrefix(source, "bool")))
- {
- *pSourceEnd = source;
- return true;
- }
- const char* p = type.c_str();
- while (*p && *source)
- {
- if (SkipSpace(p) || SkipSpace(source))
- {
- continue;
- }
- #define SKIP_EITHER_PREFIX(prefix) \
- (SkipPrefix(p, prefix) || SkipPrefix(source, prefix))
- if (SKIP_EITHER_PREFIX("const ") ||
- SKIP_EITHER_PREFIX("class ") ||
- SKIP_EITHER_PREFIX("struct ") ||
- SKIP_EITHER_PREFIX("union ") ||
- SKIP_EITHER_PREFIX("enum "))
- {
- continue;
- }
- #undef SKIP_EITHER_PREFIX
- // type may contain [...] array specifier, while source has it after field name
- if (*p == '[')
- {
- while (*p && *p++ != ']');
- continue;
- }
- // skip <...> in both
- if (SkipTemplateParameters(p) || SkipTemplateParameters(source))
- {
- continue;
- }
- // type may contain fully qualified name but source may or may not
- const char* pSkipScopeType = strstr(p, "::");
- if (pSkipScopeType && !memchr(p, ' ', pSkipScopeType - p))
- {
- pSkipScopeType += 2;
- if (strncmp(source, p, pSkipScopeType - p) == 0)
- {
- source += pSkipScopeType - p;
- }
- p = pSkipScopeType;
- continue;
- }
- if (*p == *source)
- {
- while (*p && *source && *p == *source && !strchr("<>", *p))
- {
- ++p, ++source;
- }
- continue;
- }
- if (*p != *source)
- {
- return false; // mismatch
- }
- }
- if (!*p && *source) // type match completed and having remaining source
- {
- while (*(source - 1) == ' ') --source; // try to stop after a non-space char
- *pSourceEnd = source;
- return true;
- }
- return false;
- }
- const char* MainVisitor::GetFieldTypeAnnotation(QualType qtype)
- {
- if (qtype->isPointerType())
- {
- auto type = qtype->getUnqualifiedDesugaredType()->getPointeeType().getTypePtr();
- const auto& i = _allocationTypes.find(type);
- if (i != _allocationTypes.end()
- && i->second == AllocationTypes::NonRecycler)
- {
- return "FieldNoBarrier(";
- }
- }
- return "Field(";
- }
- bool MainVisitor::VisitFunctionDecl(FunctionDecl* functionDecl)
- {
- if (functionDecl->hasBody())
- {
- CheckAllocationsInFunctionVisitor visitor(this, functionDecl);
- visitor.TraverseDecl(functionDecl);
- }
- return true;
- }
- void MainVisitor::RecordAllocation(QualType qtype, AllocationTypes allocationType)
- {
- auto type = qtype->getCanonicalTypeInternal().getTypePtr();
- _allocationTypes[type] |= allocationType;
- }
- void MainVisitor::RecordRecyclerAllocation(const string& allocationFunction, const string& type)
- {
- _allocatorTypeMap[allocationFunction].insert(type);
- }
- template <class Set, class DumpItemFunc>
- void MainVisitor::dump(const char* name, const Set& set, const DumpItemFunc& func)
- {
- Log::verbose() << "-------------------------\n\n";
- Log::verbose() << name << "\n";
- Log::verbose() << "-------------------------\n\n";
- for (auto item : set)
- {
- func(Log::verbose(), item);
- }
- Log::verbose() << "-------------------------\n\n";
- }
- template <class Item>
- void MainVisitor::dump(const char* name, const set<Item>& set)
- {
- dump(name, set, [](raw_ostream& out, const Item& item)
- {
- out << " " << item << "\n";
- });
- }
- void MainVisitor::dump(const char* name, const unordered_set<const Type*> set)
- {
- dump(name, set, [&](raw_ostream& out, const Type* type)
- {
- out << " " << QualType(type, 0).getAsString() << "\n";
- });
- }
- void MainVisitor::Inspect()
- {
- #define Dump(coll) dump(#coll, _##coll)
- Dump(pointerClasses);
- Dump(barrieredClasses);
- Log::verbose() << "Recycler allocations\n";
- for (auto item : _allocatorTypeMap)
- {
- dump(item.first.c_str(), item.second);
- }
- std::queue<const Type*> queue; // queue of types to check
- std::unordered_set<const Type*> barrierTypes; // set of types queued
- auto pushBarrierType = [&](const Type* type) -> bool
- {
- if (barrierTypes.insert(type).second)
- {
- queue.push(type);
- return true;
- }
- return false;
- };
- for (auto item : _allocationTypes)
- {
- if (item.second & AllocationTypes::WriteBarrier)
- {
- pushBarrierType(item.first);
- }
- }
- dump("WriteBarrier allocation types", barrierTypes);
- // Examine all barrierd types. They should be fully wb annotated.
- while (!queue.empty())
- {
- auto type = queue.front();
- queue.pop();
- auto r = type->getCanonicalTypeInternal()->getAsCXXRecordDecl();
- if (r)
- {
- auto typeName = r->getQualifiedNameAsString();
- ProcessUnbarrieredFields(r, pushBarrierType);
- // queue the type's base classes
- for (const auto& base: r->bases())
- {
- if (pushBarrierType(base.getType().getTypePtr()))
- {
- Log::verbose() << "Queue base type: " << base.getType().getAsString()
- << " (base of " << typeName << ")\n";
- }
- }
- }
- }
- #undef Dump
- }
- bool MainVisitor::ApplyFix()
- {
- return _fixed ? _rewriter.overwriteChangedFiles() : false;
- }
- static AllocationTypes CheckAllocationType(const CXXStaticCastExpr* castNode)
- {
- QualType targetType = castNode->getTypeAsWritten();
- if (const IdentifierInfo* info = targetType.getBaseTypeIdentifier())
- {
- return info->getName().equals("Recycler") ?
- AllocationTypes::Recycler : AllocationTypes::NonRecycler;
- }
- else
- {
- // Unknown template dependent allocator types
- return AllocationTypes::Unknown;
- }
- }
- template <class A0, class A1, class T>
- void CheckAllocationsInFunctionVisitor::VisitAllocate(
- const A0& getArg0, const A1& getArg1, const T& getAllocType)
- {
- const Expr* firstArgNode = getArg0();
- // Check if the first argument (to new or AllocateArray) is a static cast
- // AllocatorNew/AllocateArray in Chakra always does a static_cast to the AllocatorType
- const CXXStaticCastExpr* castNode = nullptr;
- if (firstArgNode != nullptr &&
- (castNode = dyn_cast<CXXStaticCastExpr>(firstArgNode)))
- {
- QualType allocatedType = getAllocType();
- string allocatedTypeStr = allocatedType.getAsString();
- auto allocationType = CheckAllocationType(castNode);
- if (allocationType == AllocationTypes::Recycler) // Recycler allocation
- {
- const Expr* secondArgNode = getArg1();
- // Chakra has two types of allocating functions- throwing and non-throwing
- // However, recycler allocations are always throwing, so the second parameter
- // should be the address of the allocator function
- auto unaryNode = cast<UnaryOperator>(secondArgNode);
- if (unaryNode != nullptr && unaryNode->getOpcode() == UnaryOperatorKind::UO_AddrOf)
- {
- Expr* subExpr = unaryNode->getSubExpr();
- if (DeclRefExpr* declRef = cast<DeclRefExpr>(subExpr))
- {
- auto declNameInfo = declRef->getNameInfo();
- auto allocationFunctionStr = declNameInfo.getName().getAsString();
- _mainVisitor->RecordRecyclerAllocation(allocationFunctionStr, allocatedTypeStr);
- if (!Contains(allocationFunctionStr, "Leaf"))
- {
- // Recycler write barrier allocation -- unless "Leaf" in allocFunc
- allocationType = AllocationTypes::RecyclerWriteBarrier;
- }
- }
- else
- {
- Log::errs() << "ERROR: (internal) Expected DeclRefExpr:\n";
- subExpr->dump();
- }
- }
- else if (auto mExpr = cast<MaterializeTemporaryExpr>(secondArgNode))
- {
- auto name = mExpr->GetTemporaryExpr()->IgnoreImpCasts()->getType().getAsString();
- if (StartsWith(name, "InfoBitsWrapper<")) // && Contains(name, "WithBarrierBit"))
- {
- // RecyclerNewEnumClass, RecyclerNewWithInfoBits -- always have WithBarrier varients
- allocationType = AllocationTypes::RecyclerWriteBarrier;
- }
- }
- else
- {
- Log::errs() << "ERROR: (internal) Expected unary node or MaterializeTemporaryExpr:\n";
- secondArgNode->dump();
- }
- }
- if (allocationType & AllocationTypes::WriteBarrier)
- {
- Log::verbose() << "In \"" << _functionDecl->getQualifiedNameAsString() << "\"\n";
- Log::verbose() << " Allocating \"" << allocatedTypeStr << "\" in write barriered memory\n";
- }
- _mainVisitor->RecordAllocation(allocatedType, allocationType);
- }
- }
- bool CheckAllocationsInFunctionVisitor::VisitCXXNewExpr(CXXNewExpr* newExpr)
- {
- if (newExpr->getNumPlacementArgs() > 1)
- {
- VisitAllocate(
- [=]() { return newExpr->getPlacementArg(0); },
- [=]() { return newExpr->getPlacementArg(1); },
- [=]() { return newExpr->getAllocatedType(); }
- );
- }
- return true;
- }
- bool CheckAllocationsInFunctionVisitor::VisitCallExpr(CallExpr* callExpr)
- {
- // Check callExpr for AllocateArray
- auto callee = callExpr->getDirectCallee();
- if (callExpr->getNumArgs() == 3 &&
- callee &&
- callee->getName().equals("AllocateArray"))
- {
- VisitAllocate(
- [=]() { return callExpr->getArg(0); },
- [=]() { return callExpr->getArg(1); },
- [=]()
- {
- auto retType = callExpr->getCallReturnType(_mainVisitor->getContext());
- return QualType(retType->getAs<PointerType>()->getPointeeType());
- }
- );
- }
- return true;
- }
- // Check if type is a "Field() *" pointer type, or alternatively a pointer to
- // any type in "alt" if provided.
- bool CheckAllocationsInFunctionVisitor::IsFieldPointer(
- const QualType& qtype, const char* alt)
- {
- if (qtype->isPointerType())
- {
- auto name = qtype->getPointeeType()
- .getDesugaredType(_mainVisitor->getContext()).getAsString();
- return StartsWith(name, "class Memory::WriteBarrierPtr<")
- || StartsWith(name, "typename WriteBarrierFieldTypeTraits<")
- || (alt && strstr(alt, name.c_str()));
- }
- return false;
- }
- bool CheckAllocationsInFunctionVisitor::CommonVisitCastExpr(CastExpr *cast)
- {
- if (IsFieldPointer(cast->getSubExpr()->getType()) && // from Field() *
- cast->getType()->isPointerType() && // to a pointer type
- !IsFieldPointer(cast->getType(), // not another Field() *
- "int|float|double|unsigned char")) // not int/float/double/byte *
- {
- _mainVisitor->ReportIllegalBarrierCast(cast->getLocStart());
- if (Log::GetLevel() >= Log::LogLevel::Info)
- {
- cast->dumpColor();
- cast->getSubExpr()->getType()->getPointeeType()
- .getDesugaredType(_mainVisitor->getContext()).dump("CAST_FROM");
- cast->getType()->getPointeeType()
- .getDesugaredType(_mainVisitor->getContext()).dump("CAST_TO");
- }
- }
- return true;
- }
- void RecyclerCheckerConsumer::HandleTranslationUnit(ASTContext& context)
- {
- MainVisitor mainVisitor(_compilerInstance, context, _fix);
- mainVisitor.TraverseDecl(context.getTranslationUnitDecl());
- mainVisitor.Inspect();
- mainVisitor.ApplyFix();
- }
- std::unique_ptr<ASTConsumer> RecyclerCheckerAction::CreateASTConsumer(
- CompilerInstance& compilerInstance, llvm::StringRef)
- {
- return llvm::make_unique<RecyclerCheckerConsumer>(compilerInstance, _fix);
- }
- bool RecyclerCheckerAction::ParseArgs(
- const CompilerInstance& compilerInstance, const std::vector<std::string>& args)
- {
- for (auto i = args.begin(); i != args.end(); i++)
- {
- if (*i == "-fix")
- {
- this->_fix = true;
- }
- else if (*i == "-info")
- {
- Log::SetLevel(Log::LogLevel::Info);
- }
- else if (*i == "-verbose")
- {
- Log::SetLevel(Log::LogLevel::Verbose);
- }
- else
- {
- Log::errs()
- << "ERROR: Unrecognized check-recycler option: " << *i << "\n"
- << "Supported options:\n"
- << " -fix Fix missing write barrier annotations"
- << " -info Log info messages\n"
- << " -verbose Log verbose messages\n";
- return false;
- }
- }
- return true;
- }
- static FrontendPluginRegistry::Add<RecyclerCheckerAction> recyclerPlugin(
- "check-recycler", "Checks the recycler allocations");
|