| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // A clang tool for the migration of Handle<T> to DirectHandle<T>. |
| // This is only useful for the V8 code base. |
| |
| #include <clang/AST/ASTContext.h> |
| #include <clang/ASTMatchers/ASTMatchFinder.h> |
| #include <clang/ASTMatchers/ASTMatchers.h> |
| #include <clang/Frontend/FrontendActions.h> |
| #include <clang/Tooling/CommonOptionsParser.h> |
| #include <clang/Tooling/Refactoring.h> |
| #include <clang/Tooling/Tooling.h> |
| #include <llvm/Support/CommandLine.h> |
| |
| #include <optional> |
| #include <regex> |
| #include <string> |
| #include <unordered_map> |
| #include <vector> |
| |
| using namespace clang; |
| using namespace clang::tooling; |
| using namespace clang::ast_matchers; |
| |
| // Command line options. |
| |
| static llvm::cl::OptionCategory my_tool_category( |
| "Handle migration tool options"); |
| static llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage); |
| |
| constexpr int kVerboseNone = 0; |
| constexpr int kVerboseReportInterestingHandleUse = 1; |
| constexpr int kVerboseReportInterestingFunctionCall = 1; |
| constexpr int kVerboseReportImplicitHandleConversion = 2; |
| constexpr int kVerboseReportHandleDereference = 2; |
| constexpr int kVerboseReportInterestingHandleDecl = 3; |
| constexpr int kVerboseReportInterestingFunctions = 4; |
| constexpr int kVerboseWhereAreWe = 90; |
| |
| static llvm::cl::opt<int> VERBOSE("verbose", |
| llvm::cl::desc("Set verbosity level"), |
| llvm::cl::value_desc("level"), |
| llvm::cl::init(kVerboseNone), |
| llvm::cl::cat(my_tool_category)); |
| |
| static llvm::cl::alias alias_for_VERBOSE("v", |
| llvm::cl::desc("Alias for --verbose"), |
| llvm::cl::aliasopt(VERBOSE), |
| llvm::cl::cat(my_tool_category)); |
| |
| static llvm::cl::extrahelp verbosity_help( |
| "Verbosity levels:\n" |
| "\t0:\tquiet\n" |
| "\t1:\tinteresting handle uses and function calls\n" |
| "\t2:\timplicit handle conversions and dereferences\n" |
| "\t3:\tinteresting handle declarations\n" |
| "\t4:\tinteresting function declarations\n" |
| "\t90:\ttrack AST source location\n" |
| "\n"); |
| |
| static llvm::cl::opt<bool> only_in_main_file( |
| "local", |
| llvm::cl::desc("Process only declarations in main file(s), default false"), |
| llvm::cl::init(false), |
| llvm::cl::cat(my_tool_category)); |
| |
| // Boilerplate. (Using clang::ast_matchers naming convention for these.) |
| // ---------------------------------------------------------------------------- |
| |
| TypeMatcher relaxType(TypeMatcher t) { |
| return anyOf(t, pointerType(pointee(t)), referenceType(pointee(t))); |
| } |
| |
| StatementMatcher constructorWithOneArgument(StatementMatcher argument) { |
| return cxxConstructExpr(argumentCountIs(1), |
| hasArgument(0, ignoringImplicit(argument))); |
| } |
| |
| auto handleDecl = cxxRecordDecl(isSameOrDerivedFrom("::v8::internal::Handle"), |
| isTemplateInstantiation()); |
| auto directHandleDecl = |
| cxxRecordDecl(isSameOrDerivedFrom("::v8::internal::DirectHandle"), |
| isTemplateInstantiation()); |
| TypeMatcher handleType = relaxType(hasDeclaration(handleDecl)); |
| TypeMatcher directHandleType = relaxType(hasDeclaration(directHandleDecl)); |
| |
| // Database for storing interesting variables, functions, etc. |
| // ---------------------------------------------------------------------------- |
| |
| template <typename NodeType> |
| struct ASTNodeHash { |
| size_t operator()(const NodeType* x) const { |
| return x->getLocation().getHashValue(); |
| } |
| }; |
| |
| template <typename NodeType> |
| struct ASTNodeEquals { |
| bool operator()(const NodeType* x, const NodeType* y) const { |
| return x->getLocation() == y->getLocation(); |
| } |
| }; |
| |
| class InterestingFunction; |
| |
| // A database with possibly interesting declarations of type Handle<T>. |
| class InterestingHandle { |
| public: |
| InterestingHandle(const InterestingHandle&) = delete; |
| InterestingHandle(InterestingHandle&&) = default; |
| |
| static InterestingHandle* Insert(const VarDecl* decl, bool is_definition) { |
| assert(decl != nullptr); |
| auto it = interesting_.find(decl); |
| if (it == interesting_.end()) { |
| auto p = |
| interesting_.emplace(decl, InterestingHandle{decl, is_definition}); |
| it = p.first; |
| } |
| return &it->second; |
| } |
| |
| static InterestingHandle* Lookup(const VarDecl* decl) { |
| assert(decl != nullptr); |
| auto it = interesting_.find(decl); |
| if (it == interesting_.end()) { |
| return nullptr; |
| } |
| return &it->second; |
| } |
| |
| void Print(const SourceManager& source_manager) const { |
| llvm::outs() << " location: "; |
| location_.print(llvm::outs(), source_manager); |
| llvm::outs() << "\n"; |
| llvm::outs() << " type: " << type_ << "\n"; |
| } |
| |
| void AddDependent(InterestingHandle* h) { |
| // Marks `h` as a dependent of `this`: `this` is the parameter of a |
| // function definition and `h` is the corresponding parameter of some |
| // declaration of the same function. |
| // 1. `this` is a definition and therefore must not be dependent to any |
| // other definition. |
| assert(dependent_to_ == nullptr); |
| // 2. `h` must not be dependent to any other definition. |
| assert(h->dependent_to_ == nullptr); |
| // 3. `h` is in a declaration and therefore it must not have dependents. |
| assert(h->list_of_dependent_.empty()); |
| list_of_dependent_.push_back(h); |
| h->dependent_to_ = this; |
| } |
| |
| void AddAsParameter(InterestingFunction* f) { |
| if (function_ != nullptr && function_ != f) { |
| llvm::outs() |
| << "Warning: adding as a parameter to a different function\n"; |
| } |
| function_ = f; |
| } |
| |
| bool CanMigrate() const; |
| Replacement GetReplacement() const { return replacement_.value(); } |
| |
| // This method registers a usage of an interesting variable. The first |
| // parameter corresponds to the AST node where the variable is used, |
| // whereas the second parameter advises if migration should be possible. |
| // We want to disallow migration if a variable is used (at least once) in a |
| // context where a `Handle<T>` is really required. When the AST is traversed |
| // and a node with a variable usage is visited, the matcher callbacks will be |
| // invoked consecutively for that node, in the order that they were added to |
| // the match finder. These callbacks, independently from one another, may |
| // invoke this method to advise whether migration should be possible. |
| // Migration is prevented when this method is called for a variable's use for |
| // the first time with migrate = false. If it has previously been called for |
| // the same variable use with migrate = true, then migration is not prevented. |
| // Evidently, the order in which the matcher callbacks are added to the finder |
| // is very important. See the comment before `HandleUseVisitor` for a detailed |
| // example. |
| void RegisterUsage(const DeclRefExpr* use, bool migrate) { |
| if (dependent_to_ != nullptr) { |
| llvm::outs() << "Warning: use of handle that is marked as dependent\n"; |
| } |
| if (use != previous_use_ && !migrate) { |
| replacement_.reset(); |
| } |
| previous_use_ = use; |
| } |
| |
| private: |
| InterestingHandle(const VarDecl* decl, bool is_definition) |
| : is_definition_(is_definition), |
| type_(decl->getType().getAsString()), |
| location_(decl->getLocation()) { |
| // If this is not a definition, bail out, otherwise let's be optimistic and |
| // generate the replacement for migration! |
| if (!is_definition_) { |
| return; |
| } |
| |
| // We get the |replacement_range| in a bit clumsy way, because clang docs |
| // for QualifiedTypeLoc explicitly say that these objects "intentionally |
| // do not provide source location for type qualifiers". |
| const auto& source_manager = decl->getASTContext().getSourceManager(); |
| const auto& options = decl->getASTContext().getLangOpts(); |
| auto first_token_loc = source_manager.getSpellingLoc(decl->getBeginLoc()); |
| auto last_token_loc = |
| source_manager.getSpellingLoc(decl->getTypeSpecEndLoc()); |
| auto end_loc = |
| Lexer::getLocForEndOfToken(last_token_loc, 0, source_manager, options); |
| auto range = CharSourceRange::getCharRange(first_token_loc, end_loc); |
| |
| auto original_text = |
| Lexer::getSourceText(range, source_manager, options).str(); |
| std::regex re_handle("\\bHandle<"); |
| auto replacement_text = |
| std::regex_replace(original_text, re_handle, "DirectHandle<"); |
| |
| if (original_text != replacement_text) { |
| replacement_.emplace(source_manager, range, replacement_text); |
| } |
| } |
| |
| bool is_definition_; |
| std::string type_; |
| SourceLocation location_; |
| std::optional<Replacement> replacement_; |
| const DeclRefExpr* previous_use_ = nullptr; |
| |
| // For parameters, this points to the corresponding function. |
| InterestingFunction* function_ = nullptr; |
| // For parameters of a function declaration, this points to the |
| // corresponding parameter of the function definition. |
| InterestingHandle* dependent_to_ = nullptr; |
| // For parameters of a function definition, this contains a list |
| // of all the corresponding parameters of function declarations (if any). |
| std::vector<InterestingHandle*> list_of_dependent_; |
| |
| using Container = std::unordered_map<const VarDecl*, |
| InterestingHandle, |
| ASTNodeHash<VarDecl>, |
| ASTNodeEquals<VarDecl>>; |
| static Container interesting_; |
| }; |
| |
| InterestingHandle::Container InterestingHandle::interesting_; |
| |
| // A database with all interesting functions. |
| class InterestingFunction { |
| public: |
| InterestingFunction(const InterestingFunction&) = delete; |
| InterestingFunction(InterestingFunction&&) = default; |
| |
| static InterestingFunction* Insert(const FunctionDecl* decl) { |
| assert(decl != nullptr); |
| auto it = interesting_.find(decl); |
| if (it == interesting_.end()) { |
| auto p = interesting_.emplace(decl, InterestingFunction{decl}); |
| it = p.first; |
| } |
| return &it->second; |
| } |
| |
| static InterestingFunction* Lookup(const FunctionDecl* decl) { |
| assert(decl != nullptr); |
| auto it = interesting_.find(decl); |
| if (it == interesting_.end()) { |
| return nullptr; |
| } |
| return &it->second; |
| } |
| |
| void AddOrCheckParameter(const ParmVarDecl* param, InterestingHandle* p) { |
| unsigned i = param->getFunctionScopeIndex(); |
| if (i >= parameters_.size()) { |
| llvm::outs() << "Warning: parameter " << i |
| << " does not exist, there are only " << parameters_.size() |
| << " parameters\n"; |
| return; |
| } |
| if (parameters_[i] == nullptr) { |
| parameters_[i] = p; |
| p->AddAsParameter(this); |
| } else if (parameters_[i] != p) { |
| const auto& source_manager = param->getASTContext().getSourceManager(); |
| llvm::outs() << "Warning: parameter " << i << " has already been added\n"; |
| llvm::outs() << " previous:\n"; |
| parameters_[i]->Print(source_manager); |
| llvm::outs() << " current:\n"; |
| p->Print(source_manager); |
| } |
| } |
| |
| void AddLocalVariable(InterestingHandle* v) { local_vars_.push_back(v); } |
| |
| const std::vector<InterestingHandle*>& parameters() const { |
| return parameters_; |
| } |
| |
| const std::vector<InterestingHandle*>& local_vars() const { |
| return local_vars_; |
| } |
| |
| bool is_special() const { return is_special_; } |
| |
| static std::set<Replacement> GetReplacements() { |
| std::set<Replacement> result; |
| for (const auto& [_, f] : interesting_) { |
| for (InterestingHandle* p : f.parameters()) { |
| if (p != nullptr && p->CanMigrate()) { |
| result.insert(p->GetReplacement()); |
| } |
| } |
| for (InterestingHandle* v : f.local_vars()) { |
| if (v->CanMigrate()) { |
| result.insert(v->GetReplacement()); |
| } |
| } |
| } |
| return result; |
| } |
| |
| private: |
| explicit InterestingFunction(const FunctionDecl* decl) |
| : name_(decl->getQualifiedNameAsString()), |
| location_(decl->getLocation()), |
| parameters_(decl->getNumParams(), nullptr) { |
| if (auto* method_decl = dyn_cast<CXXMethodDecl>(decl)) { |
| is_special_ = method_decl->isVirtual(); |
| } else { |
| is_special_ = decl->isTemplateInstantiation() || |
| decl->isFunctionTemplateSpecialization(); |
| } |
| } |
| |
| std::string name_; |
| SourceLocation location_; |
| bool is_special_ = false; |
| std::vector<InterestingHandle*> parameters_; |
| std::vector<InterestingHandle*> local_vars_; |
| |
| using Container = std::unordered_map<const FunctionDecl*, |
| InterestingFunction, |
| ASTNodeHash<FunctionDecl>, |
| ASTNodeEquals<FunctionDecl>>; |
| static Container interesting_; |
| }; |
| |
| InterestingFunction::Container InterestingFunction::interesting_; |
| |
| bool InterestingHandle::CanMigrate() const { |
| if (dependent_to_ != nullptr) { |
| return dependent_to_->CanMigrate(); |
| } |
| if (!is_definition_) { |
| return false; |
| } |
| if (function_ != nullptr && function_->is_special()) { |
| return false; |
| } |
| return replacement_.has_value(); |
| } |
| |
| // Keep track of where we are, in the AST. |
| // This is used only for debugging purposes. |
| // ---------------------------------------------------------------------------- |
| class WhereWeAreVisitor : public MatchFinder::MatchCallback { |
| private: |
| static DeclarationMatcher matcher() { return decl().bind("decl"); } |
| |
| public: |
| explicit WhereWeAreVisitor(MatchFinder& finder) { |
| finder.addMatcher(matcher(), this); |
| } |
| |
| void run(MatchFinder::MatchResult const& Result) override { |
| auto* decl = Result.Nodes.getNodeAs<Decl>("decl"); |
| assert(decl != nullptr); |
| |
| if (VERBOSE >= kVerboseWhereAreWe) { |
| ASTContext* context = Result.Context; |
| auto loc = decl->getBeginLoc(); |
| llvm::outs() << "At: " << decl->getDeclKindName() << " "; |
| loc.print(llvm::outs(), context->getSourceManager()); |
| llvm::outs() << "\n"; |
| } |
| } |
| }; |
| |
| // Find and record interesting functions: |
| // - some parameter is a Handle<T>, or |
| // - the result is a Handle<T>, or |
| // - is a method of Handle<T>, because the object pointed to by `this` is a |
| // `Handle<T>`. |
| // ---------------------------------------------------------------------------- |
| class InterestingFunctionVisitor : public MatchFinder::MatchCallback { |
| private: |
| static DeclarationMatcher matcher() { |
| return functionDecl(anyOf(hasAnyParameter(parmVarDecl(hasType(handleType))), |
| returns(handleType), |
| cxxMethodDecl(ofClass(handleDecl)))) |
| .bind("interesting-function"); |
| } |
| |
| public: |
| explicit InterestingFunctionVisitor(MatchFinder& finder, |
| bool only_in_main_file = false) |
| : only_in_main_file_(only_in_main_file) { |
| finder.addMatcher(matcher(), this); |
| } |
| |
| void run(MatchFinder::MatchResult const& Result) override { |
| auto* decl = Result.Nodes.getNodeAs<FunctionDecl>("interesting-function"); |
| assert(decl != nullptr); |
| |
| if (only_in_main_file_) { |
| ASTContext* context = Result.Context; |
| if (!context->getSourceManager().isWrittenInMainFile( |
| decl->getBeginLoc())) { |
| return; |
| } |
| } |
| |
| InterestingFunction::Insert(decl); |
| |
| if (VERBOSE >= kVerboseReportInterestingFunctions) { |
| ASTContext* context = Result.Context; |
| auto loc = decl->getBeginLoc(); |
| llvm::outs() << "Func: "; |
| loc.print(llvm::outs(), context->getSourceManager()); |
| llvm::outs() << "\n"; |
| llvm::outs() << " name " << decl->getQualifiedNameAsString() << "\n"; |
| for (const auto& param : decl->parameters()) { |
| auto type = param->getOriginalType(); |
| llvm::outs() << " param " << param->getFunctionScopeIndex() |
| << " of type " << type.getAsString() << "\n"; |
| } |
| auto result_type = decl->getCallResultType(); |
| llvm::outs() << " result " << result_type.getAsString() << "\n"; |
| llvm::outs() << " templated kind " << decl->getTemplatedKind() << "\n"; |
| } |
| } |
| |
| private: |
| bool only_in_main_file_; |
| }; |
| |
| // Find and record interesting variables. |
| // - of type Handle<T>; and |
| // - local variables or parameters. |
| // ---------------------------------------------------------------------------- |
| class HandleDeclVisitor : public MatchFinder::MatchCallback { |
| private: |
| static DeclarationMatcher matcher() { |
| return anyOf( |
| varDecl(allOf(hasLocalStorage(), hasType(handleType), |
| hasAncestor(functionDecl().bind("func-decl")))) |
| .bind("var-decl"), |
| bindingDecl(allOf(hasType(handleType), |
| hasAncestor(functionDecl().bind("func-decl")))) |
| .bind("binding-decl")); |
| } |
| |
| public: |
| explicit HandleDeclVisitor(MatchFinder& finder, |
| bool only_in_main_file = false) |
| : only_in_main_file_(only_in_main_file) { |
| finder.addMatcher(matcher(), this); |
| } |
| |
| void run(MatchFinder::MatchResult const& Result) override { |
| auto* decl = Result.Nodes.getNodeAs<VarDecl>("var-decl"); |
| if (decl == nullptr) { |
| auto* binding = Result.Nodes.getNodeAs<BindingDecl>("binding-decl"); |
| assert(binding != nullptr); |
| decl = binding->getHoldingVar(); |
| assert(decl != nullptr); |
| } |
| |
| if (only_in_main_file_) { |
| ASTContext* context = Result.Context; |
| if (!context->getSourceManager().isWrittenInMainFile( |
| decl->getBeginLoc())) { |
| return; |
| } |
| } |
| |
| auto* func_decl = Result.Nodes.getNodeAs<FunctionDecl>("func-decl"); |
| assert(func_decl != nullptr); |
| |
| if (auto* param = dyn_cast<ParmVarDecl>(decl)) { |
| auto* ctxt = param->getDeclContext(); |
| assert(ctxt != nullptr); |
| auto type = param->getType(); |
| auto* func = dyn_cast<FunctionDecl>(ctxt); |
| if (func == nullptr) { |
| // TODO(42203211): This may happen, for example, if a handle parameter |
| // is part of some other parameter's type, e.g. |
| // |
| // void f(std::function<void(Handle<HeapObject>)> g); |
| // |
| // Migrating higher-order functions is out of the scope of this tool |
| // right now. For migrating the definition of `f` here, we would need to |
| // check all its call sites and see what the actual function passed as |
| // the `g` parameter is. If the actual parameter can be migrated in all |
| // cases to a function expecting a `DirectHandle`, then `f` can be |
| // migrated, otherwise it cannot. |
| // |
| // Such cases are ignored now and we expect that they be migrated |
| // manually. |
| llvm::outs() << "Warning: this parameter does not lead to function " |
| "declaration\n"; |
| ASTContext* context = Result.Context; |
| auto loc = decl->getBeginLoc(); |
| llvm::outs() << "Decl parm: "; |
| loc.print(llvm::outs(), context->getSourceManager()); |
| llvm::outs() << "\n"; |
| llvm::outs() << " type " << type.getAsString() << "\n"; |
| llvm::outs() << " index " << param->getFunctionScopeIndex() << "\n"; |
| return; |
| } |
| if (func != func_decl) { |
| llvm::outs() << "Warning: function declaration mismatch\n"; |
| llvm::outs() << " func: " << *func << "\n"; |
| llvm::outs() << " func_decl: " << *func_decl << "\n"; |
| } |
| auto* f = InterestingFunction::Lookup(func_decl); |
| if (f != nullptr && !func_decl->isDefaulted() && |
| !func_decl->isTemplateInstantiation()) { |
| auto* p = InterestingHandle::Lookup(param); |
| if (p == nullptr) { |
| bool is_definition = func_decl->hasBody(); |
| p = InterestingHandle::Insert(param, is_definition); |
| // If there's no function definition, nothing to be done yet. |
| if (auto* func_def = func_decl->getDefinition()) { |
| if (func_def == func_decl) { |
| // If this is the function definition. |
| assert(is_definition); |
| // Look for all registered declarations of this function. |
| for (const auto& prev_decl : func_decl->redecls()) { |
| if (auto* d = InterestingFunction::Lookup(prev_decl)) { |
| // Mark the corresponding parameter of the declaration as |
| // dependent to this entry. |
| auto* q = d->parameters()[param->getFunctionScopeIndex()]; |
| if (q != nullptr) { |
| p->AddDependent(q); |
| } |
| } |
| } |
| } else if (auto* d = InterestingFunction::Lookup(func_def)) { |
| // If there is a registered function definition, mark this entry |
| // as dependent to the corresponding parameter of the function |
| // definition. |
| auto* q = d->parameters()[param->getFunctionScopeIndex()]; |
| assert(q != nullptr); |
| q->AddDependent(p); |
| } |
| } |
| } |
| f->AddOrCheckParameter(param, p); |
| } |
| |
| if (VERBOSE >= kVerboseReportInterestingHandleDecl) { |
| ASTContext* context = Result.Context; |
| auto loc = decl->getBeginLoc(); |
| llvm::outs() << "Decl parm: "; |
| loc.print(llvm::outs(), context->getSourceManager()); |
| llvm::outs() << "\n"; |
| llvm::outs() << " type " << type.getAsString() << "\n"; |
| llvm::outs() << " index " << param->getFunctionScopeIndex() << "\n"; |
| llvm::outs() << " func " << func->getQualifiedNameAsString() << "\n"; |
| } |
| } else { |
| auto* p = InterestingHandle::Insert(decl, true); |
| auto* f = InterestingFunction::Lookup(func_decl); |
| if (!func_decl->isDefaulted() && !func_decl->isTemplateInstantiation()) { |
| if (f == nullptr) { |
| assert(func_decl->hasBody()); |
| f = InterestingFunction::Insert(func_decl); |
| } |
| assert(f != nullptr); |
| f->AddLocalVariable(p); |
| } |
| |
| if (VERBOSE >= kVerboseReportInterestingHandleDecl) { |
| ASTContext* context = Result.Context; |
| auto loc = decl->getBeginLoc(); |
| llvm::outs() << "Decl var: "; |
| loc.print(llvm::outs(), context->getSourceManager()); |
| llvm::outs() << "\n" |
| << " type " << decl->getType().getAsString() << "\n"; |
| } |
| } |
| } |
| |
| private: |
| bool only_in_main_file_; |
| }; |
| |
| // Find and process uses of handle parameters or variables. |
| // |
| // Tracking such uses is important, because we want to disallow the migration of |
| // a variable's type from `Handle<T>` to `DirectHandle<T>` if the variable is |
| // used (at least once) in a context where a `Handle<T>` is really required. |
| // In general, if a variable of type `Handle<T>` is used, we need to prevent the |
| // migration of a variable. This is the purpose of `HandleUseVisitor`. However, |
| // there are cases when such a variable is used in a manner that is compatible |
| // with a `DirectHandle<T>`, e.g., when the handle is dereferenced, or |
| // implicitly converted to a direct handle. In these cases there is no need to |
| // prevent the migration and this is the purpose of more specific visitors, such |
| // as `HandleDereferenceVisitor` or `ImplicitHandleToDirectHandleVisitor` below. |
| // |
| // As mentioned in the comment before `InterestingHandle::RegisterUsage`, the |
| // order in which visitors are executed is important. For a given variable |
| // usage, migration is prevented if the first executed visitor decides to |
| // prevent it. |
| // |
| // Consider the following example: |
| // |
| // void consume_direct(DirectHandle<HeapObject> o); /* line: 1 */ |
| // Handle<HeapObject> h; /* line: 2 */ |
| // consume_direct(h); /* line: 3 */ |
| // Tagged<Map> map = h->map(); /* line: 4 */ |
| // |
| // The use of variable `h` in line 3 will be processed by two visitors: |
| // |
| // 1. `HandleUseVisitor` (this one), which will claim that the use of this |
| // variable is reason enough for disallowing its migration. |
| // 2. `ImplicitHandleToDirectHandleVisitor` (below), which will realize that |
| // the handle is implicitly converted to a direct handle, therefore we can |
| // allow its migration. |
| // |
| // Because visitor 1 is added last to the match finder (we rely on this), |
| // visitor 2 will run before visitor 1 for this node, thus not preventing the |
| // migration. |
| // |
| // Similarly, the use of variable `h` in line 4 will be processed by two |
| // visitors: first `HandleDereferenceVisitor` and then `HandleUseVisitor`, in |
| // this order, and migration will again not be prevented. As none of the |
| // variable's uses has prevented migration, the type of variable `h` in line 2 |
| // will be migrated from `Handle<HeapObject>` to `DirectHandle<HeapObject>`. |
| // ---------------------------------------------------------------------------- |
| class HandleUseVisitor : public MatchFinder::MatchCallback { |
| public: |
| static StatementMatcher matcher() { |
| return declRefExpr( |
| allOf(to(varDecl().bind("var-decl")), hasType(handleType), |
| hasAncestor( |
| functionDecl(allOf(isDefinition(), hasBody(stmt()))) |
| .bind("func-def")))) |
| .bind("handle-use"); |
| } |
| |
| public: |
| explicit HandleUseVisitor(MatchFinder& finder, bool only_in_main_file = false) |
| : only_in_main_file_(only_in_main_file) { |
| finder.addMatcher(matcher(), this); |
| } |
| |
| void run(MatchFinder::MatchResult const& Result) override { |
| auto* use = Result.Nodes.getNodeAs<DeclRefExpr>("handle-use"); |
| assert(use != nullptr); |
| auto* decl = Result.Nodes.getNodeAs<VarDecl>("var-decl"); |
| assert(decl != nullptr); |
| auto* func = Result.Nodes.getNodeAs<FunctionDecl>("func-def"); |
| assert(func != nullptr); |
| |
| if (only_in_main_file_) { |
| ASTContext* context = Result.Context; |
| if (!context->getSourceManager().isWrittenInMainFile( |
| use->getBeginLoc())) { |
| return; |
| } |
| } |
| |
| auto* h = InterestingHandle::Lookup(decl); |
| assert(h != nullptr || func->isDefaulted() || |
| func->isTemplateInstantiation()); |
| |
| if (VERBOSE >= kVerboseReportInterestingHandleUse) { |
| auto type = decl->getType(); |
| ASTContext* context = Result.Context; |
| auto loc = use->getBeginLoc(); |
| llvm::outs() << "Use var: "; |
| loc.print(llvm::outs(), context->getSourceManager()); |
| llvm::outs() << "\n"; |
| llvm::outs() << " var of type " << type.getAsString() << "\n"; |
| } |
| |
| if (h != nullptr) { |
| // This will disallow migration, unless some other more specific visitor |
| // has already run and explicitly allowed migration for the same variable |
| // usage. Here, we rely on the fact that `HandleUseVisitor` is added last |
| // to the match finder, therefore it will run last for any given AST node. |
| h->RegisterUsage(use, false); |
| } |
| } |
| |
| private: |
| bool only_in_main_file_; |
| }; |
| |
| // Find and process calls to interesting functions. |
| // Currently, this does nothing interesting except for logging. |
| // ---------------------------------------------------------------------------- |
| class CallExprWithHandleVisitor : public MatchFinder::MatchCallback { |
| private: |
| static StatementMatcher matcher() { |
| return callExpr(hasAnyArgument(hasType(handleType))).bind("call-expr"); |
| } |
| |
| public: |
| explicit CallExprWithHandleVisitor(MatchFinder& finder, |
| bool only_in_main_file = false) |
| : only_in_main_file_(only_in_main_file) { |
| finder.addMatcher(matcher(), this); |
| } |
| |
| void run(MatchFinder::MatchResult const& Result) override { |
| auto* call = Result.Nodes.getNodeAs<CallExpr>("call-expr"); |
| assert(call != nullptr); |
| |
| if (only_in_main_file_) { |
| ASTContext* context = Result.Context; |
| if (!context->getSourceManager().isWrittenInMainFile( |
| call->getBeginLoc())) { |
| return; |
| } |
| } |
| |
| auto* decl = call->getCalleeDecl(); |
| // This happens when we have a call with an unresolved expression in a |
| // template definition. |
| if (decl == nullptr) { |
| return; |
| } |
| auto* func = dyn_cast<FunctionDecl>(decl); |
| if (func == nullptr) { |
| llvm::outs() << "Warning: This is not a FunctionDecl but a " |
| << decl->getDeclKindName() << "\n"; |
| return; |
| } |
| |
| if (VERBOSE >= kVerboseReportInterestingFunctionCall) { |
| ASTContext* context = Result.Context; |
| auto loc = call->getBeginLoc(); |
| llvm::outs() << "Call: "; |
| loc.print(llvm::outs(), context->getSourceManager()); |
| llvm::outs() << "\n"; |
| llvm::outs() << " callee " << func->getQualifiedNameAsString() << "\n"; |
| |
| int i = 0; |
| for (const auto& arg : call->arguments()) { |
| auto type = arg->getType(); |
| llvm::outs() << " param " << i << " of type " << type.getAsString() |
| << "\n"; |
| ++i; |
| } |
| } |
| } |
| |
| private: |
| bool only_in_main_file_; |
| }; |
| |
| // Implicit Handle<T> -> DirectHandle<T> conversions. |
| // They do not prevent handle migration. |
| // ---------------------------------------------------------------------------- |
| class ImplicitHandleToDirectHandleVisitor : public MatchFinder::MatchCallback { |
| private: |
| static StatementMatcher matcher() { |
| return implicitCastExpr( |
| allOf(hasImplicitDestinationType(directHandleType), |
| hasCastKind(CK_ConstructorConversion), |
| hasSourceExpression( |
| constructorWithOneArgument(constructorWithOneArgument( |
| HandleUseVisitor::matcher()))))) |
| .bind("implicit-cast"); |
| } |
| |
| public: |
| explicit ImplicitHandleToDirectHandleVisitor(MatchFinder& finder, |
| bool only_in_main_file = false) |
| : only_in_main_file_(only_in_main_file) { |
| finder.addMatcher(matcher(), this); |
| } |
| |
| void run(MatchFinder::MatchResult const& Result) override { |
| auto* expr = Result.Nodes.getNodeAs<ImplicitCastExpr>("implicit-cast"); |
| assert(expr != nullptr); |
| |
| if (only_in_main_file_) { |
| ASTContext* context = Result.Context; |
| if (!context->getSourceManager().isWrittenInMainFile( |
| expr->getBeginLoc())) { |
| return; |
| } |
| } |
| |
| if (VERBOSE >= kVerboseReportImplicitHandleConversion) { |
| ASTContext* context = Result.Context; |
| auto loc = expr->getBeginLoc(); |
| llvm::outs() << "H->DH: "; |
| loc.print(llvm::outs(), context->getSourceManager()); |
| llvm::outs() << "\n"; |
| } |
| |
| auto* use = Result.Nodes.getNodeAs<DeclRefExpr>("handle-use"); |
| assert(use != nullptr); |
| auto* decl = Result.Nodes.getNodeAs<VarDecl>("var-decl"); |
| assert(decl != nullptr); |
| |
| auto* h = InterestingHandle::Lookup(decl); |
| if (h != nullptr) { |
| // This will allow migration for this variable usage. |
| h->RegisterUsage(use, true); |
| } |
| } |
| |
| private: |
| bool only_in_main_file_; |
| }; |
| |
| // Handle<T>::operator* and Handle<T>::operator-> |
| // They do not prevent handle migration. |
| // ---------------------------------------------------------------------------- |
| class HandleDereferenceVisitor : public MatchFinder::MatchCallback { |
| private: |
| static StatementMatcher matcher() { |
| return cxxOperatorCallExpr( |
| hasAnyOverloadedOperatorName("*", "->"), |
| hasAnyArgument(ignoringImplicit(HandleUseVisitor::matcher()))) |
| .bind("handle-deref"); |
| } |
| |
| public: |
| explicit HandleDereferenceVisitor(MatchFinder& finder, |
| bool only_in_main_file = false) |
| : only_in_main_file_(only_in_main_file) { |
| finder.addMatcher(matcher(), this); |
| } |
| |
| void run(MatchFinder::MatchResult const& Result) override { |
| auto* expr = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("handle-deref"); |
| assert(expr != nullptr); |
| |
| if (only_in_main_file_) { |
| ASTContext* context = Result.Context; |
| if (!context->getSourceManager().isWrittenInMainFile( |
| expr->getBeginLoc())) { |
| return; |
| } |
| } |
| |
| if (VERBOSE >= kVerboseReportHandleDereference) { |
| ASTContext* context = Result.Context; |
| auto loc = expr->getBeginLoc(); |
| llvm::outs() << "Handle deref: "; |
| loc.print(llvm::outs(), context->getSourceManager()); |
| llvm::outs() << "\n"; |
| } |
| |
| auto* use = Result.Nodes.getNodeAs<DeclRefExpr>("handle-use"); |
| assert(use != nullptr); |
| auto* decl = Result.Nodes.getNodeAs<VarDecl>("var-decl"); |
| assert(decl != nullptr); |
| |
| auto* h = InterestingHandle::Lookup(decl); |
| if (h != nullptr) { |
| // This will allow migration for this variable usage. |
| h->RegisterUsage(use, true); |
| } |
| } |
| |
| private: |
| bool only_in_main_file_; |
| }; |
| |
| // Main program. |
| // ---------------------------------------------------------------------------- |
| |
| int main(int argc, const char* argv[]) { |
| auto expected_parser = |
| CommonOptionsParser::create(argc, argv, my_tool_category); |
| if (!expected_parser) { |
| // Fail gracefully for unsupported options. |
| llvm::errs() << expected_parser.takeError(); |
| return 1; |
| } |
| CommonOptionsParser& options_parser = expected_parser.get(); |
| ClangTool Tool(options_parser.getCompilations(), |
| options_parser.getSourcePathList()); |
| |
| MatchFinder finder; |
| std::optional<WhereWeAreVisitor> where_we_are_visitor; |
| if (VERBOSE >= kVerboseWhereAreWe) { |
| where_we_are_visitor.emplace(finder); |
| } |
| |
| // These populate the database of functions and handle declarations. |
| InterestingFunctionVisitor interesting_function_visitor(finder, |
| only_in_main_file); |
| HandleDeclVisitor handle_decl_visitor(finder, only_in_main_file); |
| // These allow migration in some special cases. |
| HandleDereferenceVisitor handle_deref_callback(finder, only_in_main_file); |
| ImplicitHandleToDirectHandleVisitor implicit_conversion_visitor( |
| finder, only_in_main_file); |
| // This is not used, except for logging. |
| std::optional<CallExprWithHandleVisitor> call_expr_with_handle_visitor; |
| if (VERBOSE >= kVerboseReportInterestingFunctionCall) { |
| call_expr_with_handle_visitor.emplace(finder, only_in_main_file); |
| } |
| // This needs to be last, to disallow migration in all other cases. |
| HandleUseVisitor handle_use_callback(finder, only_in_main_file); |
| |
| int error_code = Tool.run(newFrontendActionFactory(&finder).get()); |
| if (error_code) { |
| return error_code; |
| } |
| |
| std::set<Replacement> replacements = InterestingFunction::GetReplacements(); |
| if (replacements.empty()) { |
| return 0; |
| } |
| |
| // Serialization format is documented in tools/clang/scripts/run_tool.py |
| llvm::outs() << "==== BEGIN EDITS ====\n"; |
| for (const auto& r : replacements) { |
| std::string replacement_text = r.getReplacementText().str(); |
| std::replace(replacement_text.begin(), replacement_text.end(), '\n', '\0'); |
| llvm::outs() << "r:::" << r.getFilePath() << ":::" << r.getOffset() |
| << ":::" << r.getLength() << ":::" << replacement_text << "\n"; |
| } |
| llvm::outs() << "==== END EDITS ====\n"; |
| |
| return 0; |
| } |