blob: 210b20c56d48e4fa4a32d7c5f1884e89377733f8 [file] [log] [blame]
//-------------------------------------------------------------------------------------------------------
// 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");