| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // This clang plugin checks various invariants of the Blink garbage |
| // collection infrastructure. |
| // |
| // Errors are described at: |
| // http://www.chromium.org/developers/blink-gc-plugin-errors |
| |
| #include <algorithm> |
| |
| #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" |
| #include "clang/Sema/Sema.h" |
| |
| using namespace clang; |
| using std::string; |
| |
| namespace { |
| |
| const char kClassMustLeftMostlyDeriveGC[] = |
| "[blink-gc] Class %0 must derive its GC base in the left-most position."; |
| |
| const char kClassRequiresTraceMethod[] = |
| "[blink-gc] Class %0 requires a trace method."; |
| |
| const char kBaseRequiresTracing[] = |
| "[blink-gc] Base class %0 of derived class %1 requires tracing."; |
| |
| const char kBaseRequiresTracingNote[] = |
| "[blink-gc] Untraced base class %0 declared here:"; |
| |
| 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 kClassDoesNotRequireFinalization[] = |
| "[blink-gc] Class %0 may not require finalization."; |
| |
| const char kFinalizerAccessesFinalizedField[] = |
| "[blink-gc] Finalizer %0 accesses potentially finalized field %1."; |
| |
| const char kFinalizerAccessesEagerlyFinalizedField[] = |
| "[blink-gc] Finalizer %0 accesses eagerly 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 kMemberToGCUnmanagedClassNote[] = |
| "[blink-gc] Member field %0 to non-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 kPartObjectToGCDerivedClassNote[] = |
| "[blink-gc] Part-object field %0 to a GC derived class declared here:"; |
| |
| const char kPartObjectContainsGCRootNote[] = |
| "[blink-gc] Field %0 with embedded GC root in %1 declared here:"; |
| |
| const char kFieldContainsGCRootNote[] = |
| "[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 kEagerlyFinalizedFieldNote[] = |
| "[blink-gc] Field %0 having eagerly finalized value, 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."; |
| |
| const char kClassOverridesNew[] = |
| "[blink-gc] Garbage collected class %0" |
| " is not permitted to override its new operator."; |
| |
| const char kClassDeclaresPureVirtualTrace[] = |
| "[blink-gc] Garbage collected class %0" |
| " is not permitted to declare a pure-virtual trace method."; |
| |
| const char kLeftMostBaseMustBePolymorphic[] = |
| "[blink-gc] Left-most base class %0 of derived class %1" |
| " must be polymorphic."; |
| |
| const char kBaseClassMustDeclareVirtualTrace[] = |
| "[blink-gc] Left-most base class %0 of derived class %1" |
| " must define a virtual trace method."; |
| |
| const char kClassMustDeclareGCMixinTraceMethod[] = |
| "[blink-gc] Class %0 which inherits from GarbageCollectedMixin must" |
| " locally declare and override trace(Visitor*)"; |
| |
| // Use a local RAV implementation to simply collect all FunctionDecls marked for |
| // late template parsing. This happens with the flag -fdelayed-template-parsing, |
| // which is on by default in MSVC-compatible mode. |
| std::set<FunctionDecl*> GetLateParsedFunctionDecls(TranslationUnitDecl* decl) { |
| struct Visitor : public RecursiveASTVisitor<Visitor> { |
| bool VisitFunctionDecl(FunctionDecl* function_decl) { |
| if (function_decl->isLateTemplateParsed()) |
| late_parsed_decls.insert(function_decl); |
| return true; |
| } |
| |
| std::set<FunctionDecl*> late_parsed_decls; |
| } v; |
| v.TraverseDecl(decl); |
| return v.late_parsed_decls; |
| } |
| |
| struct BlinkGCPluginOptions { |
| BlinkGCPluginOptions() |
| : enable_oilpan(false) |
| , dump_graph(false) |
| , warn_raw_ptr(false) |
| , warn_unneeded_finalizer(false) {} |
| bool enable_oilpan; |
| bool dump_graph; |
| bool warn_raw_ptr; |
| bool warn_unneeded_finalizer; |
| 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; |
| |
| // Test if a template specialization is an instantiation. |
| 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; |
| // TODO: unsupported cases. |
| case TSK_ExplicitInstantiationDeclaration: |
| return false; |
| } |
| assert(false && "Unknown template specialization kind"); |
| } |
| |
| // This visitor collects the entry points for the checker. |
| class CollectVisitor : public RecursiveASTVisitor<CollectVisitor> { |
| public: |
| CollectVisitor() {} |
| |
| RecordVector& record_decls() { return record_decls_; } |
| MethodVector& trace_decls() { return trace_decls_; } |
| |
| bool shouldVisitTemplateInstantiations() { return false; } |
| |
| // Collect record declarations, including nested declarations. |
| bool VisitCXXRecordDecl(CXXRecordDecl* record) { |
| if (record->hasDefinition() && record->isCompleteDefinition()) |
| record_decls_.push_back(record); |
| return true; |
| } |
| |
| // Collect tracing method definitions, but don't traverse method bodies. |
| bool TraverseCXXMethodDecl(CXXMethodDecl* method) { |
| if (method->isThisDeclarationADefinition() && Config::IsTraceMethod(method)) |
| trace_decls_.push_back(method); |
| return true; |
| } |
| |
| private: |
| RecordVector record_decls_; |
| MethodVector trace_decls_; |
| }; |
| |
| // This visitor checks that a finalizer method does not have invalid access to |
| // fields that are potentially finalized. A potentially finalized field is |
| // either a Member, a heap-allocated collection or an off-heap collection that |
| // contains Members. Invalid uses are currently identified as passing the field |
| // as the argument of a procedure call or using the -> or [] operators on it. |
| class CheckFinalizerVisitor |
| : public RecursiveASTVisitor<CheckFinalizerVisitor> { |
| private: |
| // Simple visitor to determine if the content of a field might be collected |
| // during finalization. |
| class MightBeCollectedVisitor : public EdgeVisitor { |
| public: |
| MightBeCollectedVisitor(bool is_eagerly_finalized) |
| : might_be_collected_(false) |
| , is_eagerly_finalized_(is_eagerly_finalized) |
| , as_eagerly_finalized_(false) {} |
| bool might_be_collected() { return might_be_collected_; } |
| bool as_eagerly_finalized() { return as_eagerly_finalized_; } |
| void VisitMember(Member* edge) override { |
| if (is_eagerly_finalized_) { |
| if (edge->ptr()->IsValue()) { |
| Value* member = static_cast<Value*>(edge->ptr()); |
| if (member->value()->IsEagerlyFinalized()) { |
| might_be_collected_ = true; |
| as_eagerly_finalized_ = true; |
| } |
| } |
| return; |
| } |
| might_be_collected_ = true; |
| } |
| void VisitCollection(Collection* edge) override { |
| if (edge->on_heap() && !is_eagerly_finalized_) { |
| might_be_collected_ = !edge->is_root(); |
| } else { |
| edge->AcceptMembers(this); |
| } |
| } |
| |
| private: |
| bool might_be_collected_; |
| bool is_eagerly_finalized_; |
| bool as_eagerly_finalized_; |
| }; |
| |
| public: |
| class Error { |
| public: |
| Error(MemberExpr *member, |
| bool as_eagerly_finalized, |
| FieldPoint* field) |
| : member_(member) |
| , as_eagerly_finalized_(as_eagerly_finalized) |
| , field_(field) {} |
| |
| MemberExpr* member_; |
| bool as_eagerly_finalized_; |
| FieldPoint* field_; |
| }; |
| |
| typedef std::vector<Error> Errors; |
| |
| CheckFinalizerVisitor(RecordCache* cache, bool is_eagerly_finalized) |
| : blacklist_context_(false) |
| , cache_(cache) |
| , is_eagerly_finalized_(is_eagerly_finalized) {} |
| |
| Errors& finalized_fields() { return finalized_fields_; } |
| |
| bool WalkUpFromCXXOperatorCallExpr(CXXOperatorCallExpr* expr) { |
| // Only continue the walk-up if the operator is a blacklisted one. |
| switch (expr->getOperator()) { |
| case OO_Arrow: |
| case OO_Subscript: |
| this->WalkUpFromCallExpr(expr); |
| default: |
| return true; |
| } |
| } |
| |
| // We consider all non-operator calls to be blacklisted contexts. |
| 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 (seen_members_.find(member) != seen_members_.end()) |
| return true; |
| |
| bool as_eagerly_finalized = false; |
| if (blacklist_context_ && |
| MightBeCollected(&it->second, as_eagerly_finalized)) { |
| finalized_fields_.push_back( |
| Error(member, as_eagerly_finalized, &it->second)); |
| seen_members_.insert(member); |
| } |
| return true; |
| } |
| |
| bool MightBeCollected(FieldPoint* point, bool& as_eagerly_finalized) { |
| MightBeCollectedVisitor visitor(is_eagerly_finalized_); |
| point->edge()->Accept(&visitor); |
| as_eagerly_finalized = visitor.as_eagerly_finalized(); |
| return visitor.might_be_collected(); |
| } |
| |
| private: |
| bool blacklist_context_; |
| Errors finalized_fields_; |
| std::set<MemberExpr*> seen_members_; |
| RecordCache* cache_; |
| bool is_eagerly_finalized_; |
| }; |
| |
| // This visitor checks that a method contains within its body, a call to a |
| // method on the provided receiver class. This is used to check manual |
| // dispatching for trace and finalize methods. |
| 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; |
| } |
| |
| bool VisitUnresolvedMemberExpr(UnresolvedMemberExpr* member) { |
| for (Decl* decl : member->decls()) { |
| if (CXXMethodDecl* method = dyn_cast<CXXMethodDecl>(decl)) { |
| if (method->getParent() == receiver_->record() && |
| Config::GetTraceMethodType(method) == |
| Config::TRACE_AFTER_DISPATCH_METHOD) { |
| dispatched_to_receiver_ = true; |
| return true; |
| } |
| } |
| } |
| return true; |
| } |
| |
| private: |
| RecordInfo* receiver_; |
| bool dispatched_to_receiver_; |
| }; |
| |
| // This visitor checks a tracing method by traversing its body. |
| // - A member field is considered traced if it is referenced in the body. |
| // - A base is traced if a base-qualified call to a trace method is found. |
| class CheckTraceVisitor : public RecursiveASTVisitor<CheckTraceVisitor> { |
| public: |
| CheckTraceVisitor(CXXMethodDecl* trace, RecordInfo* info, RecordCache* cache) |
| : trace_(trace), |
| info_(info), |
| cache_(cache), |
| delegates_to_traceimpl_(false) { |
| } |
| |
| bool delegates_to_traceimpl() const { return delegates_to_traceimpl_; } |
| |
| bool VisitMemberExpr(MemberExpr* member) { |
| // In weak callbacks, consider any occurrence as a correct usage. |
| // TODO: We really want to require that isAlive is checked on manually |
| // processed weak fields. |
| if (IsWeakCallback()) { |
| if (FieldDecl* field = dyn_cast<FieldDecl>(member->getMemberDecl())) |
| FoundField(field); |
| } |
| return true; |
| } |
| |
| bool VisitCallExpr(CallExpr* call) { |
| // In weak callbacks we don't check calls (see VisitMemberExpr). |
| if (IsWeakCallback()) |
| return true; |
| |
| Expr* callee = call->getCallee(); |
| |
| // Trace calls from a templated derived class result in a |
| // DependentScopeMemberExpr because the concrete trace call depends on the |
| // instantiation of any shared template parameters. In this case the call is |
| // "unresolved" and we resort to comparing the syntactic type names. |
| if (CXXDependentScopeMemberExpr* expr = |
| dyn_cast<CXXDependentScopeMemberExpr>(callee)) { |
| CheckCXXDependentScopeMemberExpr(call, expr); |
| return true; |
| } |
| |
| // A tracing call will have either a |visitor| or a |m_field| argument. |
| // A registerWeakMembers call will have a |this| argument. |
| if (call->getNumArgs() != 1) |
| return true; |
| Expr* arg = call->getArg(0); |
| |
| if (UnresolvedMemberExpr* expr = dyn_cast<UnresolvedMemberExpr>(callee)) { |
| // This could be a trace call of a base class, as explained in the |
| // comments of CheckTraceBaseCall(). |
| if (CheckTraceBaseCall(call)) |
| return true; |
| |
| if (expr->getMemberName().getAsString() == kRegisterWeakMembersName) |
| MarkAllWeakMembersTraced(); |
| |
| QualType base = expr->getBaseType(); |
| if (!base->isPointerType()) |
| return true; |
| CXXRecordDecl* decl = base->getPointeeType()->getAsCXXRecordDecl(); |
| if (decl) |
| CheckTraceFieldCall(expr->getMemberName().getAsString(), decl, arg); |
| if (Config::IsTraceImplName(expr->getMemberName().getAsString())) |
| delegates_to_traceimpl_ = true; |
| return true; |
| } |
| |
| if (CXXMemberCallExpr* expr = dyn_cast<CXXMemberCallExpr>(call)) { |
| if (CheckTraceFieldCall(expr) || CheckRegisterWeakMembers(expr)) |
| return true; |
| |
| if (Config::IsTraceImplName(expr->getMethodDecl()->getNameAsString())) { |
| delegates_to_traceimpl_ = true; |
| return true; |
| } |
| } |
| |
| CheckTraceBaseCall(call); |
| return true; |
| } |
| |
| private: |
| bool IsTraceCallName(const std::string& name) { |
| if (trace_->getName() == kTraceImplName) |
| return name == kTraceName; |
| if (trace_->getName() == kTraceAfterDispatchImplName) |
| return name == kTraceAfterDispatchName; |
| // Currently, a manually dispatched class cannot have mixin bases (having |
| // one would add a vtable which we explicitly check against). This means |
| // that we can only make calls to a trace method of the same name. Revisit |
| // this if our mixin/vtable assumption changes. |
| return name == trace_->getName(); |
| } |
| |
| CXXRecordDecl* GetDependentTemplatedDecl(CXXDependentScopeMemberExpr* expr) { |
| NestedNameSpecifier* qual = expr->getQualifier(); |
| if (!qual) |
| return 0; |
| |
| const Type* type = qual->getAsType(); |
| if (!type) |
| return 0; |
| |
| return RecordInfo::GetDependentTemplatedDecl(*type); |
| } |
| |
| void CheckCXXDependentScopeMemberExpr(CallExpr* call, |
| CXXDependentScopeMemberExpr* expr) { |
| string fn_name = expr->getMember().getAsString(); |
| |
| // Check for VisitorDispatcher::trace(field) and |
| // VisitorDispatcher::registerWeakMembers. |
| if (!expr->isImplicitAccess()) { |
| if (clang::DeclRefExpr* base_decl = |
| clang::dyn_cast<clang::DeclRefExpr>(expr->getBase())) { |
| if (Config::IsVisitorDispatcherType(base_decl->getType())) { |
| if (call->getNumArgs() == 1 && fn_name == kTraceName) { |
| FindFieldVisitor finder; |
| finder.TraverseStmt(call->getArg(0)); |
| if (finder.field()) |
| FoundField(finder.field()); |
| |
| return; |
| } else if (call->getNumArgs() == 1 && |
| fn_name == kRegisterWeakMembersName) { |
| MarkAllWeakMembersTraced(); |
| } |
| } |
| } |
| } |
| |
| CXXRecordDecl* tmpl = GetDependentTemplatedDecl(expr); |
| if (!tmpl) |
| return; |
| |
| // Check for Super<T>::trace(visitor) |
| if (call->getNumArgs() == 1 && IsTraceCallName(fn_name)) { |
| RecordInfo::Bases::iterator it = info_->GetBases().begin(); |
| for (; it != info_->GetBases().end(); ++it) { |
| if (it->first->getName() == tmpl->getName()) |
| it->second.MarkTraced(); |
| } |
| } |
| |
| // Check for TraceIfNeeded<T>::trace(visitor, &field) |
| if (call->getNumArgs() == 2 && fn_name == kTraceName && |
| tmpl->getName() == kTraceIfNeededName) { |
| FindFieldVisitor finder; |
| finder.TraverseStmt(call->getArg(1)); |
| if (finder.field()) |
| FoundField(finder.field()); |
| } |
| } |
| |
| bool CheckTraceBaseCall(CallExpr* call) { |
| // Checks for "Base::trace(visitor)"-like calls. |
| |
| // Checking code for these two variables is shared among MemberExpr* case |
| // and UnresolvedMemberCase* case below. |
| // |
| // For example, if we've got "Base::trace(visitor)" as |call|, |
| // callee_record will be "Base", and func_name will be "trace". |
| CXXRecordDecl* callee_record = nullptr; |
| std::string func_name; |
| |
| if (MemberExpr* callee = dyn_cast<MemberExpr>(call->getCallee())) { |
| if (!callee->hasQualifier()) |
| return false; |
| |
| FunctionDecl* trace_decl = |
| dyn_cast<FunctionDecl>(callee->getMemberDecl()); |
| if (!trace_decl || !Config::IsTraceMethod(trace_decl)) |
| return false; |
| |
| const Type* type = callee->getQualifier()->getAsType(); |
| if (!type) |
| return false; |
| |
| callee_record = type->getAsCXXRecordDecl(); |
| func_name = trace_decl->getName(); |
| } else if (UnresolvedMemberExpr* callee = |
| dyn_cast<UnresolvedMemberExpr>(call->getCallee())) { |
| // Callee part may become unresolved if the type of the argument |
| // ("visitor") is a template parameter and the called function is |
| // overloaded (i.e. trace(Visitor*) and |
| // trace(InlinedGlobalMarkingVisitor)). |
| // |
| // Here, we try to find a function that looks like trace() from the |
| // candidate overloaded functions, and if we find one, we assume it is |
| // called here. |
| |
| CXXMethodDecl* trace_decl = nullptr; |
| for (NamedDecl* named_decl : callee->decls()) { |
| if (CXXMethodDecl* method_decl = dyn_cast<CXXMethodDecl>(named_decl)) { |
| if (Config::IsTraceMethod(method_decl)) { |
| trace_decl = method_decl; |
| break; |
| } |
| } |
| } |
| if (!trace_decl) |
| return false; |
| |
| // Check if the passed argument is named "visitor". |
| if (call->getNumArgs() != 1) |
| return false; |
| DeclRefExpr* arg = dyn_cast<DeclRefExpr>(call->getArg(0)); |
| if (!arg || arg->getNameInfo().getAsString() != kVisitorVarName) |
| return false; |
| |
| callee_record = trace_decl->getParent(); |
| func_name = callee->getMemberName().getAsString(); |
| } |
| |
| if (!callee_record) |
| return false; |
| |
| if (!IsTraceCallName(func_name)) |
| return false; |
| |
| for (auto& base : info_->GetBases()) { |
| // We want to deal with omitted trace() function in an intermediary |
| // class in the class hierarchy, e.g.: |
| // class A : public GarbageCollected<A> { trace() { ... } }; |
| // class B : public A { /* No trace(); have nothing to trace. */ }; |
| // class C : public B { trace() { B::trace(visitor); } } |
| // where, B::trace() is actually A::trace(), and in some cases we get |
| // A as |callee_record| instead of B. We somehow need to mark B as |
| // traced if we find A::trace() call. |
| // |
| // To solve this, here we keep going up the class hierarchy as long as |
| // they are not required to have a trace method. The implementation is |
| // a simple DFS, where |base_records| represents the set of base classes |
| // we need to visit. |
| |
| std::vector<CXXRecordDecl*> base_records; |
| base_records.push_back(base.first); |
| |
| while (!base_records.empty()) { |
| CXXRecordDecl* base_record = base_records.back(); |
| base_records.pop_back(); |
| |
| if (base_record == callee_record) { |
| // If we find a matching trace method, pretend the user has written |
| // a correct trace() method of the base; in the example above, we |
| // find A::trace() here and mark B as correctly traced. |
| base.second.MarkTraced(); |
| return true; |
| } |
| |
| if (RecordInfo* base_info = cache_->Lookup(base_record)) { |
| if (!base_info->RequiresTraceMethod()) { |
| // If this base class is not required to have a trace method, then |
| // the actual trace method may be defined in an ancestor. |
| for (auto& inner_base : base_info->GetBases()) |
| base_records.push_back(inner_base.first); |
| } |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| bool CheckTraceFieldCall(CXXMemberCallExpr* call) { |
| return CheckTraceFieldCall(call->getMethodDecl()->getNameAsString(), |
| call->getRecordDecl(), |
| call->getArg(0)); |
| } |
| |
| bool CheckTraceFieldCall(string name, CXXRecordDecl* callee, Expr* arg) { |
| if (name != kTraceName || !Config::IsVisitor(callee->getName())) |
| return false; |
| |
| FindFieldVisitor finder; |
| finder.TraverseStmt(arg); |
| if (finder.field()) |
| FoundField(finder.field()); |
| |
| return true; |
| } |
| |
| bool CheckRegisterWeakMembers(CXXMemberCallExpr* call) { |
| CXXMethodDecl* fn = call->getMethodDecl(); |
| if (fn->getName() != kRegisterWeakMembersName) |
| return false; |
| |
| if (fn->isTemplateInstantiation()) { |
| const TemplateArgumentList& args = |
| *fn->getTemplateSpecializationInfo()->TemplateArguments; |
| // The second template argument is the callback method. |
| 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; |
| } |
| |
| class FindFieldVisitor : public RecursiveASTVisitor<FindFieldVisitor> { |
| public: |
| FindFieldVisitor() : member_(0), field_(0) {} |
| MemberExpr* member() const { return member_; } |
| FieldDecl* field() const { return field_; } |
| bool TraverseMemberExpr(MemberExpr* member) { |
| if (FieldDecl* field = dyn_cast<FieldDecl>(member->getMemberDecl())) { |
| member_ = member; |
| field_ = field; |
| return false; |
| } |
| return true; |
| } |
| private: |
| MemberExpr* member_; |
| FieldDecl* field_; |
| }; |
| |
| // Nested checking for weak callbacks. |
| CheckTraceVisitor(RecordInfo* info) |
| : trace_(nullptr), info_(info), cache_(nullptr) {} |
| |
| bool IsWeakCallback() { return !trace_; } |
| |
| void MarkTraced(RecordInfo::Fields::iterator it) { |
| // In a weak callback we can't mark strong fields as traced. |
| if (IsWeakCallback() && !it->second.edge()->IsWeakMember()) |
| return; |
| it->second.MarkTraced(); |
| } |
| |
| void FoundField(FieldDecl* field) { |
| if (IsTemplateInstantiation(info_->record())) { |
| // Pointer equality on fields does not work for template instantiations. |
| // The trace method refers to fields of the template definition which |
| // are different from the instantiated fields that need to be traced. |
| 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); |
| } |
| } |
| |
| void MarkAllWeakMembersTraced() { |
| // If we find a call to registerWeakMembers which is unresolved we |
| // unsoundly consider all weak members as traced. |
| // TODO: Find out how to validate weak member tracing for unresolved call. |
| for (auto& field : info_->GetFields()) { |
| if (field.second.edge()->IsWeakMember()) |
| field.second.MarkTraced(); |
| } |
| } |
| |
| CXXMethodDecl* trace_; |
| RecordInfo* info_; |
| RecordCache* cache_; |
| bool delegates_to_traceimpl_; |
| }; |
| |
| // This visitor checks that the fields of a class and the fields of |
| // its part objects don't define GC roots. |
| class CheckGCRootsVisitor : public RecursiveEdgeVisitor { |
| public: |
| typedef std::vector<FieldPoint*> RootPath; |
| typedef std::set<RecordInfo*> VisitingSet; |
| 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 { |
| // TODO: what should we do to check unions? |
| if (edge->value()->record()->isUnion()) |
| return; |
| |
| // Prevent infinite regress for cyclic part objects. |
| if (visiting_set_.find(edge->value()) != visiting_set_.end()) |
| return; |
| |
| visiting_set_.insert(edge->value()); |
| // If the value is a part object, then continue checking for roots. |
| for (Context::iterator it = context().begin(); |
| it != context().end(); |
| ++it) { |
| if (!(*it)->IsCollection()) |
| return; |
| } |
| ContainsGCRoots(edge->value()); |
| visiting_set_.erase(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_; |
| VisitingSet visiting_set_; |
| Errors gc_roots_; |
| }; |
| |
| // This visitor checks that the fields of a class are "well formed". |
| // - OwnPtr, RefPtr and RawPtr must not point to a GC derived types. |
| // - Part objects must not be GC derived types. |
| // - An on-heap class must never contain GC roots. |
| // - Only stack-allocated types may point to stack-allocated types. |
| class CheckFieldsVisitor : public RecursiveEdgeVisitor { |
| public: |
| |
| enum Error { |
| kRawPtrToGCManaged, |
| kRawPtrToGCManagedWarning, |
| kRefPtrToGCManaged, |
| kOwnPtrToGCManaged, |
| kMemberToGCUnmanaged, |
| kMemberInUnmanaged, |
| kPtrFromHeapToStack, |
| kGCDerivedPartObject |
| }; |
| |
| typedef std::vector<std::pair<FieldPoint*, Error> > 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 AtMember(Member* edge) override { |
| if (managed_host_) |
| return; |
| // A member is allowed to appear in the context of a root. |
| for (Context::iterator it = context().begin(); |
| it != context().end(); |
| ++it) { |
| if ((*it)->Kind() == Edge::kRoot) |
| return; |
| } |
| invalid_fields_.push_back(std::make_pair(current_, kMemberInUnmanaged)); |
| } |
| |
| void AtValue(Value* edge) override { |
| // TODO: what should we do to check unions? |
| if (edge->value()->record()->isUnion()) |
| return; |
| |
| if (!stack_allocated_host_ && edge->value()->IsStackAllocated()) { |
| invalid_fields_.push_back(std::make_pair(current_, kPtrFromHeapToStack)); |
| return; |
| } |
| |
| if (!Parent() && |
| edge->value()->IsGCDerived() && |
| !edge->value()->IsGCMixin()) { |
| invalid_fields_.push_back(std::make_pair(current_, kGCDerivedPartObject)); |
| return; |
| } |
| |
| // If in a stack allocated context, be fairly insistent that T in Member<T> |
| // is GC allocated, as stack allocated objects do not have a trace() |
| // that separately verifies the validity of Member<T>. |
| // |
| // Notice that an error is only reported if T's definition is in scope; |
| // we do not require that it must be brought into scope as that would |
| // prevent declarations of mutually dependent class types. |
| // |
| // (Note: Member<>'s constructor will at run-time verify that the |
| // pointer it wraps is indeed heap allocated.) |
| if (stack_allocated_host_ && Parent() && Parent()->IsMember() && |
| edge->value()->HasDefinition() && !edge->value()->IsGCAllocated()) { |
| invalid_fields_.push_back(std::make_pair(current_, |
| kMemberToGCUnmanaged)); |
| return; |
| } |
| |
| if (!Parent() || !edge->value()->IsGCAllocated()) |
| return; |
| |
| // In transition mode, disallow OwnPtr<T>, RawPtr<T> to GC allocated T's, |
| // also disallow T* in stack-allocated types. |
| if (options_.enable_oilpan) { |
| if (Parent()->IsOwnPtr() || |
| Parent()->IsRawPtrClass() || |
| (stack_allocated_host_ && Parent()->IsRawPtr())) { |
| invalid_fields_.push_back(std::make_pair( |
| current_, InvalidSmartPtr(Parent()))); |
| return; |
| } |
| if (options_.warn_raw_ptr && Parent()->IsRawPtr()) { |
| invalid_fields_.push_back(std::make_pair( |
| current_, kRawPtrToGCManagedWarning)); |
| } |
| return; |
| } |
| |
| if (Parent()->IsRawPtr() || Parent()->IsRefPtr() || Parent()->IsOwnPtr()) { |
| invalid_fields_.push_back(std::make_pair( |
| current_, InvalidSmartPtr(Parent()))); |
| return; |
| } |
| } |
| |
| void AtCollection(Collection* edge) override { |
| if (edge->on_heap() && Parent() && Parent()->IsOwnPtr()) |
| invalid_fields_.push_back(std::make_pair(current_, kOwnPtrToGCManaged)); |
| } |
| |
| private: |
| Error InvalidSmartPtr(Edge* ptr) { |
| if (ptr->IsRawPtr()) |
| return kRawPtrToGCManaged; |
| if (ptr->IsRefPtr()) |
| return kRefPtrToGCManaged; |
| if (ptr->IsOwnPtr()) |
| return kOwnPtrToGCManaged; |
| assert(false && "Unknown smart pointer kind"); |
| } |
| |
| const BlinkGCPluginOptions& options_; |
| FieldPoint* current_; |
| bool stack_allocated_host_; |
| bool managed_host_; |
| Errors invalid_fields_; |
| }; |
| |
| class EmptyStmtVisitor |
| : public RecursiveASTVisitor<EmptyStmtVisitor> { |
| public: |
| static bool isEmpty(Stmt* stmt) { |
| EmptyStmtVisitor visitor; |
| visitor.TraverseStmt(stmt); |
| return visitor.empty_; |
| } |
| |
| bool WalkUpFromCompoundStmt(CompoundStmt* stmt) { |
| empty_ = stmt->body_empty(); |
| return false; |
| } |
| bool VisitStmt(Stmt*) { |
| empty_ = false; |
| return false; |
| } |
| private: |
| EmptyStmtVisitor() : empty_(true) {} |
| bool empty_; |
| }; |
| |
| // Main class containing checks for various invariants of the Blink |
| // garbage collection infrastructure. |
| class BlinkGCPluginConsumer : public ASTConsumer { |
| public: |
| BlinkGCPluginConsumer(CompilerInstance& instance, |
| const BlinkGCPluginOptions& options) |
| : instance_(instance), |
| diagnostic_(instance.getDiagnostics()), |
| options_(options), |
| json_(0) { |
| |
| // Only check structures in the blink and WebKit namespaces. |
| options_.checked_namespaces.insert("blink"); |
| |
| // Ignore GC implementation files. |
| options_.ignored_directories.push_back("/heap/"); |
| |
| // Register warning/error messages. |
| diag_class_must_left_mostly_derive_gc_ = diagnostic_.getCustomDiagID( |
| getErrorLevel(), kClassMustLeftMostlyDeriveGC); |
| 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_invalid_fields_warning_ = diagnostic_.getCustomDiagID( |
| DiagnosticsEngine::Warning, kClassContainsInvalidFields); |
| diag_class_contains_gc_root_ = |
| diagnostic_.getCustomDiagID(getErrorLevel(), kClassContainsGCRoot); |
| diag_class_requires_finalization_ = diagnostic_.getCustomDiagID( |
| getErrorLevel(), kClassRequiresFinalization); |
| diag_class_does_not_require_finalization_ = diagnostic_.getCustomDiagID( |
| DiagnosticsEngine::Warning, kClassDoesNotRequireFinalization); |
| diag_finalizer_accesses_finalized_field_ = diagnostic_.getCustomDiagID( |
| getErrorLevel(), kFinalizerAccessesFinalizedField); |
| diag_finalizer_eagerly_finalized_field_ = diagnostic_.getCustomDiagID( |
| getErrorLevel(), kFinalizerAccessesEagerlyFinalizedField); |
| 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_class_overrides_new_ = |
| diagnostic_.getCustomDiagID(getErrorLevel(), kClassOverridesNew); |
| diag_class_declares_pure_virtual_trace_ = diagnostic_.getCustomDiagID( |
| getErrorLevel(), kClassDeclaresPureVirtualTrace); |
| diag_left_most_base_must_be_polymorphic_ = diagnostic_.getCustomDiagID( |
| getErrorLevel(), kLeftMostBaseMustBePolymorphic); |
| diag_base_class_must_declare_virtual_trace_ = diagnostic_.getCustomDiagID( |
| getErrorLevel(), kBaseClassMustDeclareVirtualTrace); |
| diag_class_must_declare_gc_mixin_trace_method_ = |
| diagnostic_.getCustomDiagID(getErrorLevel(), |
| kClassMustDeclareGCMixinTraceMethod); |
| |
| // Register note messages. |
| diag_base_requires_tracing_note_ = diagnostic_.getCustomDiagID( |
| DiagnosticsEngine::Note, kBaseRequiresTracingNote); |
| 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_member_to_gc_unmanaged_class_note_ = diagnostic_.getCustomDiagID( |
| DiagnosticsEngine::Note, kMemberToGCUnmanagedClassNote); |
| diag_stack_allocated_field_note_ = diagnostic_.getCustomDiagID( |
| DiagnosticsEngine::Note, kStackAllocatedFieldNote); |
| diag_member_in_unmanaged_class_note_ = diagnostic_.getCustomDiagID( |
| DiagnosticsEngine::Note, kMemberInUnmanagedClassNote); |
| diag_part_object_to_gc_derived_class_note_ = diagnostic_.getCustomDiagID( |
| DiagnosticsEngine::Note, kPartObjectToGCDerivedClassNote); |
| diag_part_object_contains_gc_root_note_ = diagnostic_.getCustomDiagID( |
| DiagnosticsEngine::Note, kPartObjectContainsGCRootNote); |
| diag_field_contains_gc_root_note_ = diagnostic_.getCustomDiagID( |
| DiagnosticsEngine::Note, kFieldContainsGCRootNote); |
| diag_finalized_field_note_ = diagnostic_.getCustomDiagID( |
| DiagnosticsEngine::Note, kFinalizedFieldNote); |
| diag_eagerly_finalized_field_note_ = diagnostic_.getCustomDiagID( |
| DiagnosticsEngine::Note, kEagerlyFinalizedFieldNote); |
| 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 { |
| // Don't run the plugin if the compilation unit is already invalid. |
| if (diagnostic_.hasErrorOccurred()) |
| return; |
| |
| ParseFunctionTemplates(context.getTranslationUnitDecl()); |
| |
| CollectVisitor visitor; |
| visitor.TraverseDecl(context.getTranslationUnitDecl()); |
| |
| if (options_.dump_graph) { |
| std::error_code err; |
| // TODO: Make createDefaultOutputFile or a shorter createOutputFile work. |
| json_ = JsonWriter::from(instance_.createOutputFile( |
| "", // OutputPath |
| err, // Errors |
| true, // Binary |
| true, // RemoveFileOnSignal |
| instance_.getFrontendOpts().OutputFile, // BaseInput |
| "graph.json", // Extension |
| false, // UseTemporary |
| false, // CreateMissingDirectories |
| 0, // ResultPathName |
| 0)); // TempPathName |
| if (!err && 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 ParseFunctionTemplates(TranslationUnitDecl* decl) { |
| if (!instance_.getLangOpts().DelayedTemplateParsing) |
| return; // Nothing to do. |
| |
| std::set<FunctionDecl*> late_parsed_decls = |
| GetLateParsedFunctionDecls(decl); |
| clang::Sema& sema = instance_.getSema(); |
| |
| for (const FunctionDecl* fd : late_parsed_decls) { |
| assert(fd->isLateTemplateParsed()); |
| |
| if (!Config::IsTraceMethod(fd)) |
| continue; |
| |
| if (instance_.getSourceManager().isInSystemHeader( |
| instance_.getSourceManager().getSpellingLoc(fd->getLocation()))) |
| continue; |
| |
| // Force parsing and AST building of the yet-uninstantiated function |
| // template trace method bodies. |
| clang::LateParsedTemplate* lpt = sema.LateParsedTemplateMap[fd]; |
| sema.LateTemplateParser(sema.OpaqueParser, *lpt); |
| } |
| } |
| |
| // Main entry for checking a record declaration. |
| void CheckRecord(RecordInfo* info) { |
| if (IsIgnored(info)) |
| return; |
| |
| CXXRecordDecl* record = info->record(); |
| |
| // TODO: what should we do to check unions? |
| if (record->isUnion()) |
| return; |
| |
| // If this is the primary template declaration, check its specializations. |
| 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); |
| } |
| |
| // Check a class-like object (eg, class, specialization, instantiation). |
| void CheckClass(RecordInfo* info) { |
| if (!info) |
| return; |
| |
| // Check consistency of stack-allocated hierarchies. |
| 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 (CXXMethodDecl* trace = info->GetTraceMethod()) { |
| if (trace->isPure()) |
| ReportClassDeclaresPureVirtualTrace(info, trace); |
| } else if (info->RequiresTraceMethod()) { |
| ReportClassRequiresTraceMethod(info); |
| } |
| |
| // Check polymorphic classes that are GC-derived or have a trace method. |
| if (info->record()->hasDefinition() && info->record()->isPolymorphic()) { |
| // TODO: Check classes that inherit a trace method. |
| CXXMethodDecl* trace = info->GetTraceMethod(); |
| if (trace || info->IsGCDerived()) |
| CheckPolymorphicClass(info, trace); |
| } |
| |
| { |
| CheckFieldsVisitor visitor(options_); |
| if (visitor.ContainsInvalidFields(info)) |
| ReportClassContainsInvalidFields(info, &visitor.invalid_fields()); |
| } |
| |
| if (info->IsGCDerived()) { |
| |
| if (!info->IsGCMixin()) { |
| CheckLeftMostDerived(info); |
| CheckDispatch(info); |
| if (CXXMethodDecl* newop = info->DeclaresNewOperator()) |
| if (!Config::IsIgnoreAnnotated(newop)) |
| ReportClassOverridesNew(info, newop); |
| if (info->IsGCMixinInstance()) { |
| // Require that declared GCMixin implementations |
| // also provide a trace() override. |
| if (info->DeclaresGCMixinMethods() |
| && !info->DeclaresLocalTraceMethod()) |
| ReportClassMustDeclareGCMixinTraceMethod(info); |
| } |
| } |
| |
| { |
| CheckGCRootsVisitor visitor; |
| if (visitor.ContainsGCRoots(info)) |
| ReportClassContainsGCRoots(info, &visitor.gc_roots()); |
| } |
| |
| if (info->NeedsFinalization()) |
| CheckFinalization(info); |
| |
| if (options_.warn_unneeded_finalizer && info->IsGCFinalized()) |
| CheckUnneededFinalization(info); |
| } |
| |
| DumpClass(info); |
| } |
| |
| CXXRecordDecl* GetDependentTemplatedDecl(const Type& type) { |
| const TemplateSpecializationType* tmpl_type = |
| type.getAs<TemplateSpecializationType>(); |
| if (!tmpl_type) |
| return 0; |
| |
| TemplateDecl* tmpl_decl = tmpl_type->getTemplateName().getAsTemplateDecl(); |
| if (!tmpl_decl) |
| return 0; |
| |
| return dyn_cast<CXXRecordDecl>(tmpl_decl->getTemplatedDecl()); |
| } |
| |
| // The GC infrastructure assumes that if the vtable of a polymorphic |
| // base-class is not initialized for a given object (ie, it is partially |
| // initialized) then the object does not need to be traced. Thus, we must |
| // ensure that any polymorphic class with a trace method does not have any |
| // tractable fields that are initialized before we are sure that the vtable |
| // and the trace method are both defined. There are two cases that need to |
| // hold to satisfy that assumption: |
| // |
| // 1. If trace is virtual, then it must be defined in the left-most base. |
| // This ensures that if the vtable is initialized then it contains a pointer |
| // to the trace method. |
| // |
| // 2. If trace is non-virtual, then the trace method is defined and we must |
| // ensure that the left-most base defines a vtable. This ensures that the |
| // first thing to be initialized when constructing the object is the vtable |
| // itself. |
| void CheckPolymorphicClass(RecordInfo* info, CXXMethodDecl* trace) { |
| CXXRecordDecl* left_most = info->record(); |
| CXXRecordDecl::base_class_iterator it = left_most->bases_begin(); |
| CXXRecordDecl* left_most_base = 0; |
| while (it != left_most->bases_end()) { |
| left_most_base = it->getType()->getAsCXXRecordDecl(); |
| if (!left_most_base && it->getType()->isDependentType()) |
| left_most_base = RecordInfo::GetDependentTemplatedDecl(*it->getType()); |
| |
| // TODO: Find a way to correctly check actual instantiations |
| // for dependent types. The escape below will be hit, eg, when |
| // we have a primary template with no definition and |
| // specializations for each case (such as SupplementBase) in |
| // which case we don't succeed in checking the required |
| // properties. |
| if (!left_most_base || !left_most_base->hasDefinition()) |
| return; |
| |
| StringRef name = left_most_base->getName(); |
| // We know GCMixin base defines virtual trace. |
| if (Config::IsGCMixinBase(name)) |
| return; |
| |
| // Stop with the left-most prior to a safe polymorphic base (a safe base |
| // is non-polymorphic and contains no fields). |
| if (Config::IsSafePolymorphicBase(name)) |
| break; |
| |
| left_most = left_most_base; |
| it = left_most->bases_begin(); |
| } |
| |
| if (RecordInfo* left_most_info = cache_.Lookup(left_most)) { |
| |
| // Check condition (1): |
| if (trace && trace->isVirtual()) { |
| if (CXXMethodDecl* trace = left_most_info->GetTraceMethod()) { |
| if (trace->isVirtual()) |
| return; |
| } |
| ReportBaseClassMustDeclareVirtualTrace(info, left_most); |
| return; |
| } |
| |
| // Check condition (2): |
| if (DeclaresVirtualMethods(left_most)) |
| return; |
| if (left_most_base) { |
| // Get the base next to the "safe polymorphic base" |
| if (it != left_most->bases_end()) |
| ++it; |
| if (it != left_most->bases_end()) { |
| if (CXXRecordDecl* next_base = it->getType()->getAsCXXRecordDecl()) { |
| if (CXXRecordDecl* next_left_most = GetLeftMostBase(next_base)) { |
| if (DeclaresVirtualMethods(next_left_most)) |
| return; |
| ReportLeftMostBaseMustBePolymorphic(info, next_left_most); |
| return; |
| } |
| } |
| } |
| } |
| ReportLeftMostBaseMustBePolymorphic(info, left_most); |
| } |
| } |
| |
| CXXRecordDecl* GetLeftMostBase(CXXRecordDecl* left_most) { |
| CXXRecordDecl::base_class_iterator it = left_most->bases_begin(); |
| while (it != left_most->bases_end()) { |
| if (it->getType()->isDependentType()) |
| left_most = RecordInfo::GetDependentTemplatedDecl(*it->getType()); |
| else |
| left_most = it->getType()->getAsCXXRecordDecl(); |
| if (!left_most || !left_most->hasDefinition()) |
| return 0; |
| it = left_most->bases_begin(); |
| } |
| return left_most; |
| } |
| |
| bool DeclaresVirtualMethods(CXXRecordDecl* decl) { |
| CXXRecordDecl::method_iterator it = decl->method_begin(); |
| for (; it != decl->method_end(); ++it) |
| if (it->isVirtual() && !it->isPure()) |
| return true; |
| return false; |
| } |
| |
| void CheckLeftMostDerived(RecordInfo* info) { |
| CXXRecordDecl* left_most = GetLeftMostBase(info->record()); |
| if (!left_most) |
| return; |
| if (!Config::IsGCBase(left_most->getName())) |
| ReportClassMustLeftMostlyDeriveGC(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(); |
| |
| // Check that dispatch methods are defined at the base. |
| if (base == info->record()) { |
| if (!trace_dispatch) |
| ReportMissingTraceDispatchMethod(info); |
| if (finalized && !finalize_dispatch) |
| ReportMissingFinalizeDispatchMethod(info); |
| if (!finalized && finalize_dispatch) { |
| ReportClassRequiresFinalization(info); |
| NoteUserDeclaredFinalizer(finalize_dispatch); |
| } |
| } |
| |
| // Check that classes implementing manual dispatch do not have vtables. |
| if (info->record()->isPolymorphic()) |
| ReportVirtualAndManualDispatch( |
| info, trace_dispatch ? trace_dispatch : finalize_dispatch); |
| |
| // If this is a non-abstract class check that it is dispatched to. |
| // TODO: Create a global variant of this local check. We can only check if |
| // the dispatch body is known in this compilation unit. |
| 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); |
| } |
| } |
| |
| // TODO: Should we collect destructors similar to trace methods? |
| void CheckFinalization(RecordInfo* info) { |
| CXXDestructorDecl* dtor = info->record()->getDestructor(); |
| |
| // For finalized classes, check the finalization method if possible. |
| if (info->IsGCFinalized()) { |
| if (dtor && dtor->hasBody()) { |
| CheckFinalizerVisitor visitor(&cache_, info->IsEagerlyFinalized()); |
| visitor.TraverseCXXMethodDecl(dtor); |
| if (!visitor.finalized_fields().empty()) { |
| ReportFinalizerAccessesFinalizedFields( |
| dtor, &visitor.finalized_fields()); |
| } |
| } |
| return; |
| } |
| |
| // Don't require finalization of a mixin that has not yet been "mixed in". |
| if (info->IsGCMixin()) |
| return; |
| |
| // Report the finalization error, and proceed to print possible causes for |
| // the finalization requirement. |
| 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 CheckUnneededFinalization(RecordInfo* info) { |
| if (!HasNonEmptyFinalizer(info)) |
| ReportClassDoesNotRequireFinalization(info); |
| } |
| |
| bool HasNonEmptyFinalizer(RecordInfo* info) { |
| CXXDestructorDecl* dtor = info->record()->getDestructor(); |
| if (dtor && dtor->isUserProvided()) { |
| if (!dtor->hasBody() || !EmptyStmtVisitor::isEmpty(dtor->getBody())) |
| return true; |
| } |
| for (RecordInfo::Bases::iterator it = info->GetBases().begin(); |
| it != info->GetBases().end(); |
| ++it) { |
| if (HasNonEmptyFinalizer(it->second.info())) |
| return true; |
| } |
| for (RecordInfo::Fields::iterator it = info->GetFields().begin(); |
| it != info->GetFields().end(); |
| ++it) { |
| if (it->second.edge()->NeedsFinalization()) |
| return true; |
| } |
| return false; |
| } |
| |
| // This is the main entry for tracing method definitions. |
| void CheckTracingMethod(CXXMethodDecl* method) { |
| RecordInfo* parent = cache_.Lookup(method->getParent()); |
| if (IsIgnored(parent)) |
| return; |
| |
| // Check templated tracing methods by checking the template instantiations. |
| // Specialized templates are handled as ordinary classes. |
| if (ClassTemplateDecl* tmpl = |
| parent->record()->getDescribedClassTemplate()) { |
| for (ClassTemplateDecl::spec_iterator it = tmpl->spec_begin(); |
| it != tmpl->spec_end(); |
| ++it) { |
| // Check trace using each template instantiation as the holder. |
| if (IsTemplateInstantiation(*it)) |
| CheckTraceOrDispatchMethod(cache_.Lookup(*it), method); |
| } |
| return; |
| } |
| |
| CheckTraceOrDispatchMethod(parent, method); |
| } |
| |
| // Determine what type of tracing method this is (dispatch or trace). |
| void CheckTraceOrDispatchMethod(RecordInfo* parent, CXXMethodDecl* method) { |
| Config::TraceMethodType trace_type = Config::GetTraceMethodType(method); |
| if (trace_type == Config::TRACE_AFTER_DISPATCH_METHOD || |
| trace_type == Config::TRACE_AFTER_DISPATCH_IMPL_METHOD || |
| !parent->GetTraceDispatchMethod()) { |
| CheckTraceMethod(parent, method, trace_type); |
| } |
| // Dispatch methods are checked when we identify subclasses. |
| } |
| |
| // Check an actual trace method. |
| void CheckTraceMethod(RecordInfo* parent, |
| CXXMethodDecl* trace, |
| Config::TraceMethodType trace_type) { |
| // A trace method must not override any non-virtual trace methods. |
| if (trace_type == Config::TRACE_METHOD) { |
| for (RecordInfo::Bases::iterator it = parent->GetBases().begin(); |
| it != parent->GetBases().end(); |
| ++it) { |
| RecordInfo* base = it->second.info(); |
| if (CXXMethodDecl* other = base->InheritsNonVirtualTrace()) |
| ReportOverriddenNonVirtualTrace(parent, trace, other); |
| } |
| } |
| |
| CheckTraceVisitor visitor(trace, parent, &cache_); |
| visitor.TraverseCXXMethodDecl(trace); |
| |
| // Skip reporting if this trace method is a just delegate to |
| // traceImpl (or traceAfterDispatchImpl) method. We will report on |
| // CheckTraceMethod on traceImpl method. |
| if (visitor.delegates_to_traceimpl()) |
| return; |
| |
| 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()) { |
| // Discontinue once an untraced-field error is found. |
| 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 { |
| // The liveness kind of a path from the point to this value |
| // is given by the innermost place that is non-strong. |
| 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())); |
| } |
| } |
| |
| // Adds either a warning or error, based on the current handling of -Werror. |
| 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) { |
| // Ignore any class prefixed by SameSizeAs. These are used in |
| // Blink to verify class sizes and don't need checking. |
| 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; // TODO: should we ignore non-existing file locations? |
| #if defined(LLVM_ON_WIN32) |
| std::replace(filename.begin(), filename.end(), '\\', '/'); |
| #endif |
| 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; |
| for (DeclContext* context = info->record()->getDeclContext(); |
| !context->isTranslationUnit(); |
| context = context->getParent()) { |
| if (NamespaceDecl* decl = dyn_cast<NamespaceDecl>(context)) { |
| if (decl->isAnonymousNamespace()) |
| return true; |
| if (options_.checked_namespaces.find(decl->getNameAsString()) != |
| options_.checked_namespaces.end()) { |
| return true; |
| } |
| } |
| } |
| 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()) { |
| // If we're in an invalid location, we're looking at things that aren't |
| // actually stated in the source. |
| return false; |
| } |
| *filename = ploc.getFilename(); |
| return true; |
| } |
| |
| void ReportClassMustLeftMostlyDeriveGC(RecordInfo* info) { |
| SourceLocation loc = info->record()->getInnerLocStart(); |
| SourceManager& manager = instance_.getSourceManager(); |
| FullSourceLoc full_loc(loc, manager); |
| diagnostic_.Report(full_loc, diag_class_must_left_mostly_derive_gc_) |
| << info->record(); |
| } |
| |
| 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::Bases::iterator it = info->GetBases().begin(); |
| it != info->GetBases().end(); |
| ++it) { |
| if (it->second.NeedsTracing().IsNeeded()) |
| NoteBaseRequiresTracing(&it->second); |
| } |
| |
| 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); |
| bool only_warnings = options_.warn_raw_ptr; |
| for (CheckFieldsVisitor::Errors::iterator it = errors->begin(); |
| only_warnings && it != errors->end(); |
| ++it) { |
| if (it->second != CheckFieldsVisitor::kRawPtrToGCManagedWarning) |
| only_warnings = false; |
| } |
| diagnostic_.Report(full_loc, only_warnings ? |
| diag_class_contains_invalid_fields_warning_ : |
| diag_class_contains_invalid_fields_) |
| << info->record(); |
| for (CheckFieldsVisitor::Errors::iterator it = errors->begin(); |
| it != errors->end(); |
| ++it) { |
| unsigned error; |
| if (it->second == CheckFieldsVisitor::kRawPtrToGCManaged || |
| it->second == CheckFieldsVisitor::kRawPtrToGCManagedWarning) { |
| error = diag_raw_ptr_to_gc_managed_class_note_; |
| } else if (it->second == CheckFieldsVisitor::kRefPtrToGCManaged) { |
| error = diag_ref_ptr_to_gc_managed_class_note_; |
| } else if (it->second == CheckFieldsVisitor::kOwnPtrToGCManaged) { |
| error = diag_own_ptr_to_gc_managed_class_note_; |
| } else if (it->second == CheckFieldsVisitor::kMemberToGCUnmanaged) { |
| error = diag_member_to_gc_unmanaged_class_note_; |
| } else if (it->second == CheckFieldsVisitor::kMemberInUnmanaged) { |
| error = diag_member_in_unmanaged_class_note_; |
| } else if (it->second == CheckFieldsVisitor::kPtrFromHeapToStack) { |
| error = diag_stack_allocated_field_note_; |
| } else if (it->second == CheckFieldsVisitor::kGCDerivedPartObject) { |
| error = diag_part_object_to_gc_derived_class_note_; |
| } else { |
| assert(false && "Unknown field error"); |
| } |
| NoteField(it->first, error); |
| } |
| } |
| |
| 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->member_->getLocStart(); |
| SourceManager& manager = instance_.getSourceManager(); |
| bool as_eagerly_finalized = it->as_eagerly_finalized_; |
| unsigned diag_error = as_eagerly_finalized ? |
| diag_finalizer_eagerly_finalized_field_ : |
| diag_finalizer_accesses_finalized_field_; |
| unsigned diag_note = as_eagerly_finalized ? |
| diag_eagerly_finalized_field_note_ : |
| diag_finalized_field_note_; |
| FullSourceLoc full_loc(loc, manager); |
| diagnostic_.Report(full_loc, diag_error) |
| << dtor << it->field_->field(); |
| NoteField(it->field_, diag_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 ReportClassDoesNotRequireFinalization(RecordInfo* info) { |
| SourceLocation loc = info->record()->getInnerLocStart(); |
| SourceManager& manager = instance_.getSourceManager(); |
| FullSourceLoc full_loc(loc, manager); |
| diagnostic_.Report(full_loc, diag_class_does_not_require_finalization_) |
| << info->record(); |
| } |
| |
| void ReportClassMustDeclareGCMixinTraceMethod(RecordInfo* info) { |
| SourceLocation loc = info->record()->getInnerLocStart(); |
| SourceManager& manager = instance_.getSourceManager(); |
| FullSourceLoc full_loc(loc, manager); |
| diagnostic_.Report( |
| full_loc, diag_class_must_declare_gc_mixin_trace_method_) |
| << 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 ReportClassOverridesNew(RecordInfo* info, CXXMethodDecl* newop) { |
| SourceLocation loc = newop->getLocStart(); |
| SourceManager& manager = instance_.getSourceManager(); |
| FullSourceLoc full_loc(loc, manager); |
| diagnostic_.Report(full_loc, diag_class_overrides_new_) << info->record(); |
| } |
| |
| void ReportClassDeclaresPureVirtualTrace(RecordInfo* info, |
| CXXMethodDecl* trace) { |
| SourceLocation loc = trace->getLocStart(); |
| SourceManager& manager = instance_.getSourceManager(); |
| FullSourceLoc full_loc(loc, manager); |
| diagnostic_.Report(full_loc, diag_class_declares_pure_virtual_trace_) |
| << info->record(); |
| } |
| |
| void ReportLeftMostBaseMustBePolymorphic(RecordInfo* derived, |
| CXXRecordDecl* base) { |
| SourceLocation loc = base->getLocStart(); |
| SourceManager& manager = instance_.getSourceManager(); |
| FullSourceLoc full_loc(loc, manager); |
| diagnostic_.Report(full_loc, diag_left_most_base_must_be_polymorphic_) |
| << base << derived->record(); |
| } |
| |
| void ReportBaseClassMustDeclareVirtualTrace(RecordInfo* derived, |
| CXXRecordDecl* base) { |
| SourceLocation loc = base->getLocStart(); |
| SourceManager& manager = instance_.getSourceManager(); |
| FullSourceLoc full_loc(loc, manager); |
| diagnostic_.Report(full_loc, diag_base_class_must_declare_virtual_trace_) |
| << base << derived->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 NoteBaseRequiresTracing(BasePoint* base) { |
| SourceLocation loc = base->spec().getLocStart(); |
| SourceManager& manager = instance_.getSourceManager(); |
| FullSourceLoc full_loc(loc, manager); |
| diagnostic_.Report(full_loc, diag_base_requires_tracing_note_) |
| << base->info()->record(); |
| } |
| |
| 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_must_left_mostly_derive_gc_; |
| 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_invalid_fields_warning_; |
| unsigned diag_class_contains_gc_root_; |
| unsigned diag_class_requires_finalization_; |
| unsigned diag_class_does_not_require_finalization_; |
| unsigned diag_finalizer_accesses_finalized_field_; |
| unsigned diag_finalizer_eagerly_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_class_overrides_new_; |
| unsigned diag_class_declares_pure_virtual_trace_; |
| unsigned diag_left_most_base_must_be_polymorphic_; |
| unsigned diag_base_class_must_declare_virtual_trace_; |
| unsigned diag_class_must_declare_gc_mixin_trace_method_; |
| |
| unsigned diag_base_requires_tracing_note_; |
| 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_member_to_gc_unmanaged_class_note_; |
| unsigned diag_stack_allocated_field_note_; |
| unsigned diag_member_in_unmanaged_class_note_; |
| unsigned diag_part_object_to_gc_derived_class_note_; |
| unsigned diag_part_object_contains_gc_root_note_; |
| unsigned diag_field_contains_gc_root_note_; |
| unsigned diag_finalized_field_note_; |
| unsigned diag_eagerly_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: |
| // Overridden from PluginASTAction: |
| virtual std::unique_ptr<ASTConsumer> CreateASTConsumer( |
| CompilerInstance& instance, |
| llvm::StringRef ref) { |
| return llvm::make_unique<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 if (args[i] == "warn-raw-ptr") { |
| options_.warn_raw_ptr = true; |
| } else if (args[i] == "warn-unneeded-finalizer") { |
| options_.warn_unneeded_finalizer = true; |
| } else { |
| parsed = false; |
| llvm::errs() << "Unknown blink-gc-plugin argument: " << args[i] << "\n"; |
| } |
| } |
| |
| return parsed; |
| } |
| |
| private: |
| BlinkGCPluginOptions options_; |
| }; |
| |
| } // namespace |
| |
| static FrontendPluginRegistry::Add<BlinkGCPluginAction> X( |
| "blink-gc-plugin", |
| "Check Blink GC invariants"); |