This source file includes following definitions.
- IsTemplateInstantiation
- record_decls
- trace_decls
- shouldVisitTemplateInstantiations
- VisitCXXRecordDecl
- TraverseCXXMethodDecl
- might_be_collected
- VisitMember
- VisitCollection
- cache_
- finalized_fields
- WalkUpFromCXXOperatorCallExpr
- WalkUpFromCallExpr
- VisitMemberExpr
- MightBeCollected
- dispatched_to_receiver_
- dispatched_to_receiver
- VisitMemberExpr
- info_
- VisitMemberExpr
- info_
- IsWeakCallback
- MarkTraced
- gc_roots
- ContainsGCRoots
- VisitValue
- VisitPersistent
- AtCollection
- stack_allocated_host_
- invalid_fields
- ContainsInvalidFields
- VisitMember
- VisitValue
- json_
- HandleTranslationUnit
- CheckRecord
- CheckClass
- CheckDispatch
- CheckFinalization
- CheckTracingMethod
- CheckTraceOrDispatchMethod
- CheckTraceMethod
- DumpClass
- DumpEdge
- DumpField
- AtValue
- getErrorLevel
- GetLocString
- IsIgnored
- IsIgnoredClass
- InIgnoredDirectory
- InCheckedNamespace
- GetFilename
- ReportClassRequiresTraceMethod
- ReportBaseRequiresTracing
- ReportFieldsRequireTracing
- ReportClassContainsInvalidFields
- ReportClassContainsGCRoots
- ReportFinalizerAccessesFinalizedFields
- ReportClassRequiresFinalization
- ReportOverriddenNonVirtualTrace
- ReportMissingTraceDispatchMethod
- ReportMissingFinalizeDispatchMethod
- ReportMissingDispatchMethod
- ReportVirtualAndManualDispatch
- ReportMissingTraceDispatch
- ReportMissingFinalizeDispatch
- ReportMissingDispatch
- ReportDerivesNonStackAllocated
- NoteManualDispatchMethod
- NoteFieldRequiresTracing
- NotePartObjectContainsGCRoot
- NoteFieldContainsGCRoot
- NoteUserDeclaredDestructor
- NoteUserDeclaredFinalizer
- NoteBaseRequiresFinalization
- NoteField
- NoteField
- NoteOverriddenNonVirtualTrace
- CreateASTConsumer
- ParseArgs
#include "Config.h"
#include "JsonWriter.h"
#include "RecordInfo.h"
#include "clang/AST/AST.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendPluginRegistry.h"
using namespace clang;
using std::string;
namespace {
const char kClassRequiresTraceMethod[] =
"[blink-gc] Class %0 requires a trace method"
" because it contains fields that require tracing.";
const char kBaseRequiresTracing[] =
"[blink-gc] Base class %0 of derived class %1 requires tracing.";
const char kFieldsRequireTracing[] =
"[blink-gc] Class %0 has untraced fields that require tracing.";
const char kFieldRequiresTracingNote[] =
"[blink-gc] Untraced field %0 declared here:";
const char kClassContainsInvalidFields[] =
"[blink-gc] Class %0 contains invalid fields.";
const char kClassContainsGCRoot[] =
"[blink-gc] Class %0 contains GC root in field %1.";
const char kClassRequiresFinalization[] =
"[blink-gc] Class %0 requires finalization.";
const char kFinalizerAccessesFinalizedField[] =
"[blink-gc] Finalizer %0 accesses potentially finalized field %1.";
const char kRawPtrToGCManagedClassNote[] =
"[blink-gc] Raw pointer field %0 to a GC managed class declared here:";
const char kRefPtrToGCManagedClassNote[] =
"[blink-gc] RefPtr field %0 to a GC managed class declared here:";
const char kOwnPtrToGCManagedClassNote[] =
"[blink-gc] OwnPtr field %0 to a GC managed class declared here:";
const char kStackAllocatedFieldNote[] =
"[blink-gc] Stack-allocated field %0 declared here:";
const char kMemberInUnmanagedClassNote[] =
"[blink-gc] Member field %0 in unmanaged class declared here:";
const char kPartObjectContainsGCRoot[] =
"[blink-gc] Field %0 with embedded GC root in %1 declared here:";
const char kFieldContainsGCRoot[] =
"[blink-gc] Field %0 defining a GC root declared here:";
const char kOverriddenNonVirtualTrace[] =
"[blink-gc] Class %0 overrides non-virtual trace of base class %1.";
const char kOverriddenNonVirtualTraceNote[] =
"[blink-gc] Non-virtual trace method declared here:";
const char kMissingTraceDispatchMethod[] =
"[blink-gc] Class %0 is missing manual trace dispatch.";
const char kMissingFinalizeDispatchMethod[] =
"[blink-gc] Class %0 is missing manual finalize dispatch.";
const char kVirtualAndManualDispatch[] =
"[blink-gc] Class %0 contains or inherits virtual methods"
" but implements manual dispatching.";
const char kMissingTraceDispatch[] =
"[blink-gc] Missing dispatch to class %0 in manual trace dispatch.";
const char kMissingFinalizeDispatch[] =
"[blink-gc] Missing dispatch to class %0 in manual finalize dispatch.";
const char kFinalizedFieldNote[] =
"[blink-gc] Potentially finalized field %0 declared here:";
const char kUserDeclaredDestructorNote[] =
"[blink-gc] User-declared destructor declared here:";
const char kUserDeclaredFinalizerNote[] =
"[blink-gc] User-declared finalizer declared here:";
const char kBaseRequiresFinalizationNote[] =
"[blink-gc] Base class %0 requiring finalization declared here:";
const char kFieldRequiresFinalizationNote[] =
"[blink-gc] Field %0 requiring finalization declared here:";
const char kManualDispatchMethodNote[] =
"[blink-gc] Manual dispatch %0 declared here:";
const char kDerivesNonStackAllocated[] =
"[blink-gc] Stack-allocated class %0 derives class %1"
" which is not stack allocated.";
struct BlinkGCPluginOptions {
BlinkGCPluginOptions() : enable_oilpan(false), dump_graph(false) {}
bool enable_oilpan;
bool dump_graph;
std::set<std::string> ignored_classes;
std::set<std::string> checked_namespaces;
std::vector<std::string> ignored_directories;
};
typedef std::vector<CXXRecordDecl*> RecordVector;
typedef std::vector<CXXMethodDecl*> MethodVector;
static bool IsTemplateInstantiation(CXXRecordDecl* record) {
ClassTemplateSpecializationDecl* spec =
dyn_cast<ClassTemplateSpecializationDecl>(record);
if (!spec)
return false;
switch (spec->getTemplateSpecializationKind()) {
case TSK_ImplicitInstantiation:
case TSK_ExplicitInstantiationDefinition:
return true;
case TSK_Undeclared:
case TSK_ExplicitSpecialization:
return false;
case TSK_ExplicitInstantiationDeclaration:
return false;
}
assert(false && "Unknown template specialization kind");
}
class CollectVisitor : public RecursiveASTVisitor<CollectVisitor> {
public:
CollectVisitor() {}
RecordVector& record_decls() { return record_decls_; }
MethodVector& trace_decls() { return trace_decls_; }
bool shouldVisitTemplateInstantiations() { return false; }
bool VisitCXXRecordDecl(CXXRecordDecl* record) {
if (record->hasDefinition() && record->isCompleteDefinition())
record_decls_.push_back(record);
return true;
}
bool TraverseCXXMethodDecl(CXXMethodDecl* method) {
if (method->isThisDeclarationADefinition() && Config::IsTraceMethod(method))
trace_decls_.push_back(method);
return true;
}
private:
RecordVector record_decls_;
MethodVector trace_decls_;
};
class CheckFinalizerVisitor
: public RecursiveASTVisitor<CheckFinalizerVisitor> {
private:
class MightBeCollectedVisitor : public EdgeVisitor {
public:
MightBeCollectedVisitor() : might_be_collected_(false) {}
bool might_be_collected() { return might_be_collected_; }
void VisitMember(Member* edge) override { might_be_collected_ = true; }
void VisitCollection(Collection* edge) override {
if (edge->on_heap()) {
might_be_collected_ = !edge->is_root();
} else {
edge->AcceptMembers(this);
}
}
private:
bool might_be_collected_;
};
public:
typedef std::vector<std::pair<MemberExpr*, FieldPoint*> > Errors;
CheckFinalizerVisitor(RecordCache* cache)
: blacklist_context_(false), cache_(cache) {}
Errors& finalized_fields() { return finalized_fields_; }
bool WalkUpFromCXXOperatorCallExpr(CXXOperatorCallExpr* expr) {
switch (expr->getOperator()) {
case OO_Arrow:
case OO_Subscript:
this->WalkUpFromCallExpr(expr);
default:
return true;
}
}
bool WalkUpFromCallExpr(CallExpr* expr) {
bool prev_blacklist_context = blacklist_context_;
blacklist_context_ = true;
for (size_t i = 0; i < expr->getNumArgs(); ++i)
this->TraverseStmt(expr->getArg(i));
blacklist_context_ = prev_blacklist_context;
return true;
}
bool VisitMemberExpr(MemberExpr* member) {
FieldDecl* field = dyn_cast<FieldDecl>(member->getMemberDecl());
if (!field)
return true;
RecordInfo* info = cache_->Lookup(field->getParent());
if (!info)
return true;
RecordInfo::Fields::iterator it = info->GetFields().find(field);
if (it == info->GetFields().end())
return true;
if (blacklist_context_ && MightBeCollected(&it->second))
finalized_fields_.push_back(std::make_pair(member, &it->second));
return true;
}
bool MightBeCollected(FieldPoint* point) {
MightBeCollectedVisitor visitor;
point->edge()->Accept(&visitor);
return visitor.might_be_collected();
}
private:
bool blacklist_context_;
Errors finalized_fields_;
RecordCache* cache_;
};
class CheckDispatchVisitor : public RecursiveASTVisitor<CheckDispatchVisitor> {
public:
CheckDispatchVisitor(RecordInfo* receiver)
: receiver_(receiver), dispatched_to_receiver_(false) {}
bool dispatched_to_receiver() { return dispatched_to_receiver_; }
bool VisitMemberExpr(MemberExpr* member) {
if (CXXMethodDecl* fn = dyn_cast<CXXMethodDecl>(member->getMemberDecl())) {
if (fn->getParent() == receiver_->record())
dispatched_to_receiver_ = true;
}
return true;
}
private:
RecordInfo* receiver_;
bool dispatched_to_receiver_;
};
class CheckTraceVisitor : public RecursiveASTVisitor<CheckTraceVisitor> {
public:
CheckTraceVisitor(CXXMethodDecl* trace, RecordInfo* info)
: trace_(trace), info_(info) {}
bool VisitMemberExpr(MemberExpr* member) {
if (FieldDecl* field = dyn_cast<FieldDecl>(member->getMemberDecl())) {
if (IsTemplateInstantiation(info_->record())) {
const string& name = field->getNameAsString();
for (RecordInfo::Fields::iterator it = info_->GetFields().begin();
it != info_->GetFields().end();
++it) {
if (it->first->getNameAsString() == name) {
MarkTraced(it);
break;
}
}
} else {
RecordInfo::Fields::iterator it = info_->GetFields().find(field);
if (it != info_->GetFields().end())
MarkTraced(it);
}
return true;
}
if (IsWeakCallback())
return true;
if (CXXMethodDecl* fn = dyn_cast<CXXMethodDecl>(member->getMemberDecl())) {
const string& name = fn->getNameAsString();
if (name == kRegisterWeakMembersName) {
if (fn->isTemplateInstantiation()) {
const TemplateArgumentList& args =
*fn->getTemplateSpecializationInfo()->TemplateArguments;
if (args.size() > 1 &&
args[1].getKind() == TemplateArgument::Declaration) {
if (FunctionDecl* callback =
dyn_cast<FunctionDecl>(args[1].getAsDecl())) {
if (callback->hasBody()) {
CheckTraceVisitor nested_visitor(info_);
nested_visitor.TraverseStmt(callback->getBody());
}
}
}
}
return true;
}
if (Config::IsTraceMethod(fn) && member->hasQualifier()) {
if (const Type* type = member->getQualifier()->getAsType()) {
if (CXXRecordDecl* decl = type->getAsCXXRecordDecl()) {
RecordInfo::Bases::iterator it = info_->GetBases().find(decl);
if (it != info_->GetBases().end())
it->second.MarkTraced();
}
}
}
}
return true;
}
private:
CheckTraceVisitor(RecordInfo* info) : trace_(0), info_(info) {}
bool IsWeakCallback() { return !trace_; }
void MarkTraced(RecordInfo::Fields::iterator it) {
if (IsWeakCallback() && !it->second.edge()->IsWeakMember())
return;
it->second.MarkTraced();
}
CXXMethodDecl* trace_;
RecordInfo* info_;
};
class CheckGCRootsVisitor : public RecursiveEdgeVisitor {
public:
typedef std::vector<FieldPoint*> RootPath;
typedef std::vector<RootPath> Errors;
CheckGCRootsVisitor() {}
Errors& gc_roots() { return gc_roots_; }
bool ContainsGCRoots(RecordInfo* info) {
for (RecordInfo::Fields::iterator it = info->GetFields().begin();
it != info->GetFields().end();
++it) {
current_.push_back(&it->second);
it->second.edge()->Accept(this);
current_.pop_back();
}
return !gc_roots_.empty();
}
void VisitValue(Value* edge) override {
if (edge->value()->record()->isUnion())
return;
for (Context::iterator it = context().begin();
it != context().end();
++it) {
if (!(*it)->IsCollection())
return;
}
ContainsGCRoots(edge->value());
}
void VisitPersistent(Persistent* edge) override {
gc_roots_.push_back(current_);
}
void AtCollection(Collection* edge) override {
if (edge->is_root())
gc_roots_.push_back(current_);
}
protected:
RootPath current_;
Errors gc_roots_;
};
class CheckFieldsVisitor : public RecursiveEdgeVisitor {
public:
typedef std::vector<std::pair<FieldPoint*, Edge*> > Errors;
CheckFieldsVisitor(const BlinkGCPluginOptions& options)
: options_(options), current_(0), stack_allocated_host_(false) {}
Errors& invalid_fields() { return invalid_fields_; }
bool ContainsInvalidFields(RecordInfo* info) {
stack_allocated_host_ = info->IsStackAllocated();
managed_host_ = stack_allocated_host_ ||
info->IsGCAllocated() ||
info->IsNonNewable() ||
info->IsOnlyPlacementNewable();
for (RecordInfo::Fields::iterator it = info->GetFields().begin();
it != info->GetFields().end();
++it) {
context().clear();
current_ = &it->second;
current_->edge()->Accept(this);
}
return !invalid_fields_.empty();
}
void VisitMember(Member* edge) override {
if (managed_host_)
return;
for (Context::iterator it = context().begin();
it != context().end();
++it) {
if ((*it)->Kind() == Edge::kRoot)
return;
}
invalid_fields_.push_back(std::make_pair(current_, edge));
}
void VisitValue(Value* edge) override {
if (edge->value()->record()->isUnion())
return;
if (!stack_allocated_host_ && edge->value()->IsStackAllocated()) {
invalid_fields_.push_back(std::make_pair(current_, edge));
return;
}
if (!Parent() || !edge->value()->IsGCAllocated())
return;
if (Parent()->IsOwnPtr() ||
(stack_allocated_host_ && Parent()->IsRawPtr() &&
!edge->value()->IsTreeShared())) {
invalid_fields_.push_back(std::make_pair(current_, Parent()));
return;
}
if (options_.enable_oilpan)
return;
if (Parent()->IsRawPtr() || Parent()->IsRefPtr())
invalid_fields_.push_back(std::make_pair(current_, Parent()));
}
private:
const BlinkGCPluginOptions& options_;
FieldPoint* current_;
bool stack_allocated_host_;
bool managed_host_;
Errors invalid_fields_;
};
class BlinkGCPluginConsumer : public ASTConsumer {
public:
BlinkGCPluginConsumer(CompilerInstance& instance,
const BlinkGCPluginOptions& options)
: instance_(instance),
diagnostic_(instance.getDiagnostics()),
options_(options),
json_(0) {
options_.checked_namespaces.insert("blink");
options_.checked_namespaces.insert("WebCore");
options_.checked_namespaces.insert("WebKit");
options_.ignored_directories.push_back("/heap/");
diag_class_requires_trace_method_ =
diagnostic_.getCustomDiagID(getErrorLevel(), kClassRequiresTraceMethod);
diag_base_requires_tracing_ =
diagnostic_.getCustomDiagID(getErrorLevel(), kBaseRequiresTracing);
diag_fields_require_tracing_ =
diagnostic_.getCustomDiagID(getErrorLevel(), kFieldsRequireTracing);
diag_class_contains_invalid_fields_ = diagnostic_.getCustomDiagID(
getErrorLevel(), kClassContainsInvalidFields);
diag_class_contains_gc_root_ =
diagnostic_.getCustomDiagID(getErrorLevel(), kClassContainsGCRoot);
diag_class_requires_finalization_ = diagnostic_.getCustomDiagID(
getErrorLevel(), kClassRequiresFinalization);
diag_finalizer_accesses_finalized_field_ = diagnostic_.getCustomDiagID(
getErrorLevel(), kFinalizerAccessesFinalizedField);
diag_overridden_non_virtual_trace_ = diagnostic_.getCustomDiagID(
getErrorLevel(), kOverriddenNonVirtualTrace);
diag_missing_trace_dispatch_method_ = diagnostic_.getCustomDiagID(
getErrorLevel(), kMissingTraceDispatchMethod);
diag_missing_finalize_dispatch_method_ = diagnostic_.getCustomDiagID(
getErrorLevel(), kMissingFinalizeDispatchMethod);
diag_virtual_and_manual_dispatch_ =
diagnostic_.getCustomDiagID(getErrorLevel(), kVirtualAndManualDispatch);
diag_missing_trace_dispatch_ =
diagnostic_.getCustomDiagID(getErrorLevel(), kMissingTraceDispatch);
diag_missing_finalize_dispatch_ =
diagnostic_.getCustomDiagID(getErrorLevel(), kMissingFinalizeDispatch);
diag_derives_non_stack_allocated_ =
diagnostic_.getCustomDiagID(getErrorLevel(), kDerivesNonStackAllocated);
diag_field_requires_tracing_note_ = diagnostic_.getCustomDiagID(
DiagnosticsEngine::Note, kFieldRequiresTracingNote);
diag_raw_ptr_to_gc_managed_class_note_ = diagnostic_.getCustomDiagID(
DiagnosticsEngine::Note, kRawPtrToGCManagedClassNote);
diag_ref_ptr_to_gc_managed_class_note_ = diagnostic_.getCustomDiagID(
DiagnosticsEngine::Note, kRefPtrToGCManagedClassNote);
diag_own_ptr_to_gc_managed_class_note_ = diagnostic_.getCustomDiagID(
DiagnosticsEngine::Note, kOwnPtrToGCManagedClassNote);
diag_stack_allocated_field_note_ = diagnostic_.getCustomDiagID(
DiagnosticsEngine::Note, kStackAllocatedFieldNote);
diag_member_in_unmanaged_class_note_ = diagnostic_.getCustomDiagID(
DiagnosticsEngine::Note, kMemberInUnmanagedClassNote);
diag_part_object_contains_gc_root_note_ = diagnostic_.getCustomDiagID(
DiagnosticsEngine::Note, kPartObjectContainsGCRoot);
diag_field_contains_gc_root_note_ = diagnostic_.getCustomDiagID(
DiagnosticsEngine::Note, kFieldContainsGCRoot);
diag_finalized_field_note_ = diagnostic_.getCustomDiagID(
DiagnosticsEngine::Note, kFinalizedFieldNote);
diag_user_declared_destructor_note_ = diagnostic_.getCustomDiagID(
DiagnosticsEngine::Note, kUserDeclaredDestructorNote);
diag_user_declared_finalizer_note_ = diagnostic_.getCustomDiagID(
DiagnosticsEngine::Note, kUserDeclaredFinalizerNote);
diag_base_requires_finalization_note_ = diagnostic_.getCustomDiagID(
DiagnosticsEngine::Note, kBaseRequiresFinalizationNote);
diag_field_requires_finalization_note_ = diagnostic_.getCustomDiagID(
DiagnosticsEngine::Note, kFieldRequiresFinalizationNote);
diag_overridden_non_virtual_trace_note_ = diagnostic_.getCustomDiagID(
DiagnosticsEngine::Note, kOverriddenNonVirtualTraceNote);
diag_manual_dispatch_method_note_ = diagnostic_.getCustomDiagID(
DiagnosticsEngine::Note, kManualDispatchMethodNote);
}
void HandleTranslationUnit(ASTContext& context) override {
CollectVisitor visitor;
visitor.TraverseDecl(context.getTranslationUnitDecl());
if (options_.dump_graph) {
string err;
json_ = JsonWriter::from(instance_.createOutputFile(
"",
err,
true,
true,
instance_.getFrontendOpts().OutputFile,
"graph.json",
false,
false,
0,
0));
if (err.empty() && json_) {
json_->OpenList();
} else {
json_ = 0;
llvm::errs()
<< "[blink-gc] "
<< "Failed to create an output file for the object graph.\n";
}
}
for (RecordVector::iterator it = visitor.record_decls().begin();
it != visitor.record_decls().end();
++it) {
CheckRecord(cache_.Lookup(*it));
}
for (MethodVector::iterator it = visitor.trace_decls().begin();
it != visitor.trace_decls().end();
++it) {
CheckTracingMethod(*it);
}
if (json_) {
json_->CloseList();
delete json_;
json_ = 0;
}
}
void CheckRecord(RecordInfo* info) {
if (IsIgnored(info))
return;
CXXRecordDecl* record = info->record();
if (record->isUnion())
return;
if (record->isThisDeclarationADefinition() &&
record->getDescribedClassTemplate()) {
ClassTemplateDecl* tmpl = record->getDescribedClassTemplate();
for (ClassTemplateDecl::spec_iterator it = tmpl->spec_begin();
it != tmpl->spec_end();
++it) {
CheckClass(cache_.Lookup(*it));
}
return;
}
CheckClass(info);
}
void CheckClass(RecordInfo* info) {
if (!info)
return;
if (info->IsStackAllocated()) {
for (RecordInfo::Bases::iterator it = info->GetBases().begin();
it != info->GetBases().end();
++it) {
if (!it->second.info()->IsStackAllocated())
ReportDerivesNonStackAllocated(info, &it->second);
}
}
if (info->RequiresTraceMethod() && !info->GetTraceMethod())
ReportClassRequiresTraceMethod(info);
{
CheckFieldsVisitor visitor(options_);
if (visitor.ContainsInvalidFields(info))
ReportClassContainsInvalidFields(info, &visitor.invalid_fields());
}
if (info->IsGCDerived()) {
CheckDispatch(info);
if (!info->IsTreeShared()) {
CheckGCRootsVisitor visitor;
if (visitor.ContainsGCRoots(info))
ReportClassContainsGCRoots(info, &visitor.gc_roots());
}
if (info->NeedsFinalization())
CheckFinalization(info);
}
DumpClass(info);
}
void CheckDispatch(RecordInfo* info) {
bool finalized = info->IsGCFinalized();
CXXMethodDecl* trace_dispatch = info->GetTraceDispatchMethod();
CXXMethodDecl* finalize_dispatch = info->GetFinalizeDispatchMethod();
if (!trace_dispatch && !finalize_dispatch)
return;
CXXRecordDecl* base = trace_dispatch ? trace_dispatch->getParent()
: finalize_dispatch->getParent();
if (base == info->record()) {
if (!trace_dispatch)
ReportMissingTraceDispatchMethod(info);
if (finalized && !finalize_dispatch)
ReportMissingFinalizeDispatchMethod(info);
if (!finalized && finalize_dispatch) {
ReportClassRequiresFinalization(info);
NoteUserDeclaredFinalizer(finalize_dispatch);
}
}
if (info->record()->isPolymorphic())
ReportVirtualAndManualDispatch(
info, trace_dispatch ? trace_dispatch : finalize_dispatch);
if (info->IsConsideredAbstract())
return;
const FunctionDecl* defn;
if (trace_dispatch && trace_dispatch->isDefined(defn)) {
CheckDispatchVisitor visitor(info);
visitor.TraverseStmt(defn->getBody());
if (!visitor.dispatched_to_receiver())
ReportMissingTraceDispatch(defn, info);
}
if (finalized && finalize_dispatch && finalize_dispatch->isDefined(defn)) {
CheckDispatchVisitor visitor(info);
visitor.TraverseStmt(defn->getBody());
if (!visitor.dispatched_to_receiver())
ReportMissingFinalizeDispatch(defn, info);
}
}
void CheckFinalization(RecordInfo* info) {
CXXDestructorDecl* dtor = info->record()->getDestructor();
if (info->IsGCFinalized()) {
if (dtor && dtor->hasBody()) {
CheckFinalizerVisitor visitor(&cache_);
visitor.TraverseCXXMethodDecl(dtor);
if (!visitor.finalized_fields().empty()) {
ReportFinalizerAccessesFinalizedFields(
dtor, &visitor.finalized_fields());
}
}
return;
}
if (info->IsGCMixin())
return;
ReportClassRequiresFinalization(info);
if (dtor && dtor->isUserProvided())
NoteUserDeclaredDestructor(dtor);
for (RecordInfo::Bases::iterator it = info->GetBases().begin();
it != info->GetBases().end();
++it) {
if (it->second.info()->NeedsFinalization())
NoteBaseRequiresFinalization(&it->second);
}
for (RecordInfo::Fields::iterator it = info->GetFields().begin();
it != info->GetFields().end();
++it) {
if (it->second.edge()->NeedsFinalization())
NoteField(&it->second, diag_field_requires_finalization_note_);
}
}
void CheckTracingMethod(CXXMethodDecl* method) {
RecordInfo* parent = cache_.Lookup(method->getParent());
if (IsIgnored(parent))
return;
if (ClassTemplateDecl* tmpl =
parent->record()->getDescribedClassTemplate()) {
for (ClassTemplateDecl::spec_iterator it = tmpl->spec_begin();
it != tmpl->spec_end();
++it) {
if (IsTemplateInstantiation(*it))
CheckTraceOrDispatchMethod(cache_.Lookup(*it), method);
}
return;
}
CheckTraceOrDispatchMethod(parent, method);
}
void CheckTraceOrDispatchMethod(RecordInfo* parent, CXXMethodDecl* method) {
bool isTraceAfterDispatch;
if (Config::IsTraceMethod(method, &isTraceAfterDispatch)) {
if (isTraceAfterDispatch || !parent->GetTraceDispatchMethod()) {
CheckTraceMethod(parent, method, isTraceAfterDispatch);
}
}
}
void CheckTraceMethod(RecordInfo* parent,
CXXMethodDecl* trace,
bool isTraceAfterDispatch) {
if (!isTraceAfterDispatch && !trace->isVirtual()) {
for (RecordInfo::Bases::iterator it = parent->GetBases().begin();
it != parent->GetBases().end();
++it) {
RecordInfo* base = it->second.info();
if (base->IsGCMixin())
continue;
if (CXXMethodDecl* other = base->InheritsNonVirtualTrace())
ReportOverriddenNonVirtualTrace(parent, trace, other);
}
}
CheckTraceVisitor visitor(trace, parent);
visitor.TraverseCXXMethodDecl(trace);
for (RecordInfo::Bases::iterator it = parent->GetBases().begin();
it != parent->GetBases().end();
++it) {
if (!it->second.IsProperlyTraced())
ReportBaseRequiresTracing(parent, trace, it->first);
}
for (RecordInfo::Fields::iterator it = parent->GetFields().begin();
it != parent->GetFields().end();
++it) {
if (!it->second.IsProperlyTraced()) {
ReportFieldsRequireTracing(parent, trace);
break;
}
}
}
void DumpClass(RecordInfo* info) {
if (!json_)
return;
json_->OpenObject();
json_->Write("name", info->record()->getQualifiedNameAsString());
json_->Write("loc", GetLocString(info->record()->getLocStart()));
json_->CloseObject();
class DumpEdgeVisitor : public RecursiveEdgeVisitor {
public:
DumpEdgeVisitor(JsonWriter* json) : json_(json) {}
void DumpEdge(RecordInfo* src,
RecordInfo* dst,
const string& lbl,
const Edge::LivenessKind& kind,
const string& loc) {
json_->OpenObject();
json_->Write("src", src->record()->getQualifiedNameAsString());
json_->Write("dst", dst->record()->getQualifiedNameAsString());
json_->Write("lbl", lbl);
json_->Write("kind", kind);
json_->Write("loc", loc);
json_->Write("ptr",
!Parent() ? "val" :
Parent()->IsRawPtr() ? "raw" :
Parent()->IsRefPtr() ? "ref" :
Parent()->IsOwnPtr() ? "own" :
(Parent()->IsMember() ||
Parent()->IsWeakMember()) ? "mem" :
"val");
json_->CloseObject();
}
void DumpField(RecordInfo* src, FieldPoint* point, const string& loc) {
src_ = src;
point_ = point;
loc_ = loc;
point_->edge()->Accept(this);
}
void AtValue(Value* e) override {
Edge::LivenessKind kind = Edge::kStrong;
if (Config::IsIgnoreCycleAnnotated(point_->field())) {
kind = Edge::kWeak;
} else {
for (Context::iterator it = context().begin();
it != context().end();
++it) {
Edge::LivenessKind pointer_kind = (*it)->Kind();
if (pointer_kind != Edge::kStrong) {
kind = pointer_kind;
break;
}
}
}
DumpEdge(
src_, e->value(), point_->field()->getNameAsString(), kind, loc_);
}
private:
JsonWriter* json_;
RecordInfo* src_;
FieldPoint* point_;
string loc_;
};
DumpEdgeVisitor visitor(json_);
RecordInfo::Bases& bases = info->GetBases();
for (RecordInfo::Bases::iterator it = bases.begin();
it != bases.end();
++it) {
visitor.DumpEdge(info,
it->second.info(),
"<super>",
Edge::kStrong,
GetLocString(it->second.spec().getLocStart()));
}
RecordInfo::Fields& fields = info->GetFields();
for (RecordInfo::Fields::iterator it = fields.begin();
it != fields.end();
++it) {
visitor.DumpField(info,
&it->second,
GetLocString(it->second.field()->getLocStart()));
}
}
DiagnosticsEngine::Level getErrorLevel() {
return diagnostic_.getWarningsAsErrors() ? DiagnosticsEngine::Error
: DiagnosticsEngine::Warning;
}
const string GetLocString(SourceLocation loc) {
const SourceManager& source_manager = instance_.getSourceManager();
PresumedLoc ploc = source_manager.getPresumedLoc(loc);
if (ploc.isInvalid())
return "";
string loc_str;
llvm::raw_string_ostream OS(loc_str);
OS << ploc.getFilename()
<< ":" << ploc.getLine()
<< ":" << ploc.getColumn();
return OS.str();
}
bool IsIgnored(RecordInfo* record) {
return !record ||
!InCheckedNamespace(record) ||
IsIgnoredClass(record) ||
InIgnoredDirectory(record);
}
bool IsIgnoredClass(RecordInfo* info) {
const string SameSizeAs = "SameSizeAs";
if (info->name().compare(0, SameSizeAs.size(), SameSizeAs) == 0)
return true;
return options_.ignored_classes.find(info->name()) !=
options_.ignored_classes.end();
}
bool InIgnoredDirectory(RecordInfo* info) {
string filename;
if (!GetFilename(info->record()->getLocStart(), &filename))
return false;
std::vector<string>::iterator it = options_.ignored_directories.begin();
for (; it != options_.ignored_directories.end(); ++it)
if (filename.find(*it) != string::npos)
return true;
return false;
}
bool InCheckedNamespace(RecordInfo* info) {
if (!info)
return false;
DeclContext* context = info->record()->getDeclContext();
if (context->isRecord())
return InCheckedNamespace(cache_.Lookup(context));
if (context->isNamespace()) {
const NamespaceDecl* decl = dyn_cast<NamespaceDecl>(context);
if (decl->isAnonymousNamespace())
return false;
return options_.checked_namespaces.find(decl->getNameAsString()) !=
options_.checked_namespaces.end();
}
return false;
}
bool GetFilename(SourceLocation loc, string* filename) {
const SourceManager& source_manager = instance_.getSourceManager();
SourceLocation spelling_location = source_manager.getSpellingLoc(loc);
PresumedLoc ploc = source_manager.getPresumedLoc(spelling_location);
if (ploc.isInvalid()) {
return false;
}
*filename = ploc.getFilename();
return true;
}
void ReportClassRequiresTraceMethod(RecordInfo* info) {
SourceLocation loc = info->record()->getInnerLocStart();
SourceManager& manager = instance_.getSourceManager();
FullSourceLoc full_loc(loc, manager);
diagnostic_.Report(full_loc, diag_class_requires_trace_method_)
<< info->record();
for (RecordInfo::Fields::iterator it = info->GetFields().begin();
it != info->GetFields().end();
++it) {
if (!it->second.IsProperlyTraced())
NoteFieldRequiresTracing(info, it->first);
}
}
void ReportBaseRequiresTracing(RecordInfo* derived,
CXXMethodDecl* trace,
CXXRecordDecl* base) {
SourceLocation loc = trace->getLocStart();
SourceManager& manager = instance_.getSourceManager();
FullSourceLoc full_loc(loc, manager);
diagnostic_.Report(full_loc, diag_base_requires_tracing_)
<< base << derived->record();
}
void ReportFieldsRequireTracing(RecordInfo* info, CXXMethodDecl* trace) {
SourceLocation loc = trace->getLocStart();
SourceManager& manager = instance_.getSourceManager();
FullSourceLoc full_loc(loc, manager);
diagnostic_.Report(full_loc, diag_fields_require_tracing_)
<< info->record();
for (RecordInfo::Fields::iterator it = info->GetFields().begin();
it != info->GetFields().end();
++it) {
if (!it->second.IsProperlyTraced())
NoteFieldRequiresTracing(info, it->first);
}
}
void ReportClassContainsInvalidFields(RecordInfo* info,
CheckFieldsVisitor::Errors* errors) {
SourceLocation loc = info->record()->getLocStart();
SourceManager& manager = instance_.getSourceManager();
FullSourceLoc full_loc(loc, manager);
diagnostic_.Report(full_loc, diag_class_contains_invalid_fields_)
<< info->record();
for (CheckFieldsVisitor::Errors::iterator it = errors->begin();
it != errors->end();
++it) {
if (it->second->IsRawPtr()) {
NoteField(it->first, diag_raw_ptr_to_gc_managed_class_note_);
} else if (it->second->IsRefPtr()) {
NoteField(it->first, diag_ref_ptr_to_gc_managed_class_note_);
} else if (it->second->IsOwnPtr()) {
NoteField(it->first, diag_own_ptr_to_gc_managed_class_note_);
} else if (it->second->IsMember()) {
NoteField(it->first, diag_member_in_unmanaged_class_note_);
} else if (it->second->IsValue()) {
NoteField(it->first, diag_stack_allocated_field_note_);
}
}
}
void ReportClassContainsGCRoots(RecordInfo* info,
CheckGCRootsVisitor::Errors* errors) {
SourceLocation loc = info->record()->getLocStart();
SourceManager& manager = instance_.getSourceManager();
FullSourceLoc full_loc(loc, manager);
for (CheckGCRootsVisitor::Errors::iterator it = errors->begin();
it != errors->end();
++it) {
CheckGCRootsVisitor::RootPath::iterator path = it->begin();
FieldPoint* point = *path;
diagnostic_.Report(full_loc, diag_class_contains_gc_root_)
<< info->record() << point->field();
while (++path != it->end()) {
NotePartObjectContainsGCRoot(point);
point = *path;
}
NoteFieldContainsGCRoot(point);
}
}
void ReportFinalizerAccessesFinalizedFields(
CXXMethodDecl* dtor,
CheckFinalizerVisitor::Errors* fields) {
for (CheckFinalizerVisitor::Errors::iterator it = fields->begin();
it != fields->end();
++it) {
SourceLocation loc = it->first->getLocStart();
SourceManager& manager = instance_.getSourceManager();
FullSourceLoc full_loc(loc, manager);
diagnostic_.Report(full_loc, diag_finalizer_accesses_finalized_field_)
<< dtor << it->second->field();
NoteField(it->second, diag_finalized_field_note_);
}
}
void ReportClassRequiresFinalization(RecordInfo* info) {
SourceLocation loc = info->record()->getInnerLocStart();
SourceManager& manager = instance_.getSourceManager();
FullSourceLoc full_loc(loc, manager);
diagnostic_.Report(full_loc, diag_class_requires_finalization_)
<< info->record();
}
void ReportOverriddenNonVirtualTrace(RecordInfo* info,
CXXMethodDecl* trace,
CXXMethodDecl* overridden) {
SourceLocation loc = trace->getLocStart();
SourceManager& manager = instance_.getSourceManager();
FullSourceLoc full_loc(loc, manager);
diagnostic_.Report(full_loc, diag_overridden_non_virtual_trace_)
<< info->record() << overridden->getParent();
NoteOverriddenNonVirtualTrace(overridden);
}
void ReportMissingTraceDispatchMethod(RecordInfo* info) {
ReportMissingDispatchMethod(info, diag_missing_trace_dispatch_method_);
}
void ReportMissingFinalizeDispatchMethod(RecordInfo* info) {
ReportMissingDispatchMethod(info, diag_missing_finalize_dispatch_method_);
}
void ReportMissingDispatchMethod(RecordInfo* info, unsigned error) {
SourceLocation loc = info->record()->getInnerLocStart();
SourceManager& manager = instance_.getSourceManager();
FullSourceLoc full_loc(loc, manager);
diagnostic_.Report(full_loc, error) << info->record();
}
void ReportVirtualAndManualDispatch(RecordInfo* info,
CXXMethodDecl* dispatch) {
SourceLocation loc = info->record()->getInnerLocStart();
SourceManager& manager = instance_.getSourceManager();
FullSourceLoc full_loc(loc, manager);
diagnostic_.Report(full_loc, diag_virtual_and_manual_dispatch_)
<< info->record();
NoteManualDispatchMethod(dispatch);
}
void ReportMissingTraceDispatch(const FunctionDecl* dispatch,
RecordInfo* receiver) {
ReportMissingDispatch(dispatch, receiver, diag_missing_trace_dispatch_);
}
void ReportMissingFinalizeDispatch(const FunctionDecl* dispatch,
RecordInfo* receiver) {
ReportMissingDispatch(dispatch, receiver, diag_missing_finalize_dispatch_);
}
void ReportMissingDispatch(const FunctionDecl* dispatch,
RecordInfo* receiver,
unsigned error) {
SourceLocation loc = dispatch->getLocStart();
SourceManager& manager = instance_.getSourceManager();
FullSourceLoc full_loc(loc, manager);
diagnostic_.Report(full_loc, error) << receiver->record();
}
void ReportDerivesNonStackAllocated(RecordInfo* info, BasePoint* base) {
SourceLocation loc = base->spec().getLocStart();
SourceManager& manager = instance_.getSourceManager();
FullSourceLoc full_loc(loc, manager);
diagnostic_.Report(full_loc, diag_derives_non_stack_allocated_)
<< info->record() << base->info()->record();
}
void NoteManualDispatchMethod(CXXMethodDecl* dispatch) {
SourceLocation loc = dispatch->getLocStart();
SourceManager& manager = instance_.getSourceManager();
FullSourceLoc full_loc(loc, manager);
diagnostic_.Report(full_loc, diag_manual_dispatch_method_note_) << dispatch;
}
void NoteFieldRequiresTracing(RecordInfo* holder, FieldDecl* field) {
NoteField(field, diag_field_requires_tracing_note_);
}
void NotePartObjectContainsGCRoot(FieldPoint* point) {
FieldDecl* field = point->field();
SourceLocation loc = field->getLocStart();
SourceManager& manager = instance_.getSourceManager();
FullSourceLoc full_loc(loc, manager);
diagnostic_.Report(full_loc, diag_part_object_contains_gc_root_note_)
<< field << field->getParent();
}
void NoteFieldContainsGCRoot(FieldPoint* point) {
NoteField(point, diag_field_contains_gc_root_note_);
}
void NoteUserDeclaredDestructor(CXXMethodDecl* dtor) {
SourceLocation loc = dtor->getLocStart();
SourceManager& manager = instance_.getSourceManager();
FullSourceLoc full_loc(loc, manager);
diagnostic_.Report(full_loc, diag_user_declared_destructor_note_);
}
void NoteUserDeclaredFinalizer(CXXMethodDecl* dtor) {
SourceLocation loc = dtor->getLocStart();
SourceManager& manager = instance_.getSourceManager();
FullSourceLoc full_loc(loc, manager);
diagnostic_.Report(full_loc, diag_user_declared_finalizer_note_);
}
void NoteBaseRequiresFinalization(BasePoint* base) {
SourceLocation loc = base->spec().getLocStart();
SourceManager& manager = instance_.getSourceManager();
FullSourceLoc full_loc(loc, manager);
diagnostic_.Report(full_loc, diag_base_requires_finalization_note_)
<< base->info()->record();
}
void NoteField(FieldPoint* point, unsigned note) {
NoteField(point->field(), note);
}
void NoteField(FieldDecl* field, unsigned note) {
SourceLocation loc = field->getLocStart();
SourceManager& manager = instance_.getSourceManager();
FullSourceLoc full_loc(loc, manager);
diagnostic_.Report(full_loc, note) << field;
}
void NoteOverriddenNonVirtualTrace(CXXMethodDecl* overridden) {
SourceLocation loc = overridden->getLocStart();
SourceManager& manager = instance_.getSourceManager();
FullSourceLoc full_loc(loc, manager);
diagnostic_.Report(full_loc, diag_overridden_non_virtual_trace_note_)
<< overridden;
}
unsigned diag_class_requires_trace_method_;
unsigned diag_base_requires_tracing_;
unsigned diag_fields_require_tracing_;
unsigned diag_class_contains_invalid_fields_;
unsigned diag_class_contains_gc_root_;
unsigned diag_class_requires_finalization_;
unsigned diag_finalizer_accesses_finalized_field_;
unsigned diag_overridden_non_virtual_trace_;
unsigned diag_missing_trace_dispatch_method_;
unsigned diag_missing_finalize_dispatch_method_;
unsigned diag_virtual_and_manual_dispatch_;
unsigned diag_missing_trace_dispatch_;
unsigned diag_missing_finalize_dispatch_;
unsigned diag_derives_non_stack_allocated_;
unsigned diag_field_requires_tracing_note_;
unsigned diag_raw_ptr_to_gc_managed_class_note_;
unsigned diag_ref_ptr_to_gc_managed_class_note_;
unsigned diag_own_ptr_to_gc_managed_class_note_;
unsigned diag_stack_allocated_field_note_;
unsigned diag_member_in_unmanaged_class_note_;
unsigned diag_part_object_contains_gc_root_note_;
unsigned diag_field_contains_gc_root_note_;
unsigned diag_finalized_field_note_;
unsigned diag_user_declared_destructor_note_;
unsigned diag_user_declared_finalizer_note_;
unsigned diag_base_requires_finalization_note_;
unsigned diag_field_requires_finalization_note_;
unsigned diag_overridden_non_virtual_trace_note_;
unsigned diag_manual_dispatch_method_note_;
CompilerInstance& instance_;
DiagnosticsEngine& diagnostic_;
BlinkGCPluginOptions options_;
RecordCache cache_;
JsonWriter* json_;
};
class BlinkGCPluginAction : public PluginASTAction {
public:
BlinkGCPluginAction() {}
protected:
virtual ASTConsumer* CreateASTConsumer(CompilerInstance& instance,
llvm::StringRef ref) {
return new BlinkGCPluginConsumer(instance, options_);
}
virtual bool ParseArgs(const CompilerInstance& instance,
const std::vector<string>& args) {
bool parsed = true;
for (size_t i = 0; i < args.size() && parsed; ++i) {
if (args[i] == "enable-oilpan") {
options_.enable_oilpan = true;
} else if (args[i] == "dump-graph") {
options_.dump_graph = true;
} else {
parsed = false;
llvm::errs() << "Unknown blink-gc-plugin argument: " << args[i] << "\n";
}
}
return parsed;
}
private:
BlinkGCPluginOptions options_;
};
}
static FrontendPluginRegistry::Add<BlinkGCPluginAction> X(
"blink-gc-plugin",
"Check Blink GC invariants");