| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "CheckLayoutObjectMethodsVisitor.h" |
| |
| #include "clang/AST/AST.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| #include "clang/ASTMatchers/ASTMatchers.h" |
| |
| using namespace clang::ast_matchers; |
| |
| namespace chrome_checker { |
| |
| std::string CheckLayoutObjectMethodsVisitor::layout_directory = |
| "third_party/blink/renderer/core/layout"; |
| std::string CheckLayoutObjectMethodsVisitor::test_directory = |
| "tools/clang/plugins/tests"; |
| |
| namespace { |
| |
| const char kLayoutObjectMethodWithoutIsNotDestroyedCheck[] = |
| "[layout] LayoutObject's method %0 in %1 must call CheckIsNotDestroyed() " |
| "at the beginning."; |
| |
| class DiagnosticsReporter { |
| public: |
| explicit DiagnosticsReporter(clang::CompilerInstance& instance) |
| : instance_(instance), diagnostic_(instance.getDiagnostics()) { |
| diag_layout_object_method_without_is_not_destroyed_check_ = |
| diagnostic_.getCustomDiagID( |
| getErrorLevel(), kLayoutObjectMethodWithoutIsNotDestroyedCheck); |
| } |
| |
| bool hasErrorOccurred() const { return diagnostic_.hasErrorOccurred(); } |
| |
| clang::DiagnosticsEngine::Level getErrorLevel() const { |
| return diagnostic_.getWarningsAsErrors() |
| ? clang::DiagnosticsEngine::Error |
| : clang::DiagnosticsEngine::Warning; |
| } |
| |
| void LayoutObjectMethodWithoutIsNotDestroyedCheck( |
| const clang::CXXMethodDecl* expr, |
| const clang::CXXRecordDecl* record) { |
| ReportDiagnostic(expr->getBeginLoc(), |
| diag_layout_object_method_without_is_not_destroyed_check_) |
| << expr << record << expr->getSourceRange(); |
| } |
| |
| private: |
| clang::DiagnosticBuilder ReportDiagnostic(clang::SourceLocation location, |
| unsigned diag_id) { |
| clang::SourceManager& manager = instance_.getSourceManager(); |
| clang::FullSourceLoc full_loc(location, manager); |
| return diagnostic_.Report(full_loc, diag_id); |
| } |
| |
| clang::CompilerInstance& instance_; |
| clang::DiagnosticsEngine& diagnostic_; |
| |
| unsigned diag_layout_object_method_without_is_not_destroyed_check_; |
| }; |
| |
| class LayoutObjectMethodMatcher : public MatchFinder::MatchCallback { |
| public: |
| explicit LayoutObjectMethodMatcher(class DiagnosticsReporter& diagnostics) |
| : diagnostics_(diagnostics) {} |
| |
| void Register(MatchFinder& match_finder) { |
| const DeclarationMatcher function_call = |
| cxxMethodDecl( |
| hasParent( |
| cxxRecordDecl(isSameOrDerivedFrom("::blink::LayoutObject"))), |
| has(compoundStmt()), |
| // Avoid matching the following cases |
| unless(anyOf(isConstexpr(), isDefaulted(), isPure(), |
| cxxConstructorDecl(), cxxDestructorDecl(), |
| isStaticStorageClass(), |
| // Do not trace lambdas (no name, possibly tracking |
| // more parameters than intended because of [&]). |
| hasParent(cxxRecordDecl(isLambda())), |
| // Do not include CheckIsDestroyed() itself. |
| hasName("CheckIsNotDestroyed"), |
| // Do not include tracing methods. |
| hasName("Trace"), hasName("TraceAfterDispatch")))) |
| .bind("layout_method"); |
| match_finder.addDynamicMatcher(function_call, this); |
| } |
| |
| void run(const MatchFinder::MatchResult& result) override { |
| auto* method = |
| result.Nodes.getNodeAs<clang::CXXMethodDecl>("layout_method"); |
| |
| const auto* stmt = method->getBody(); |
| assert(stmt); |
| |
| if (!llvm::dyn_cast<clang::CompoundStmt>(stmt)->body_empty()) { |
| auto* stmts = llvm::dyn_cast<clang::CompoundStmt>(stmt)->body_front(); |
| if (clang::CXXMemberCallExpr::classof(stmts)) { |
| auto* call = llvm::dyn_cast<clang::CXXMemberCallExpr>(stmts); |
| const std::string& name = call->getMethodDecl()->getNameAsString(); |
| if (name == "CheckIsNotDestroyed") |
| return; |
| } |
| } |
| |
| auto* type = method->getParent(); |
| diagnostics_.LayoutObjectMethodWithoutIsNotDestroyedCheck(method, type); |
| } |
| |
| private: |
| DiagnosticsReporter& diagnostics_; |
| }; |
| |
| } // namespace |
| |
| CheckLayoutObjectMethodsVisitor::CheckLayoutObjectMethodsVisitor( |
| clang::CompilerInstance& compiler) |
| : compiler_(compiler) {} |
| |
| void CheckLayoutObjectMethodsVisitor::VisitLayoutObjectMethods( |
| clang::ASTContext& ast_context) { |
| const clang::FileEntry* file_entry = |
| ast_context.getSourceManager().getFileEntryForID( |
| ast_context.getSourceManager().getMainFileID()); |
| if (!file_entry) |
| return; |
| |
| auto file_name_ref = file_entry->tryGetRealPathName(); |
| if (file_name_ref.empty()) |
| return; |
| std::string file_name = file_name_ref.str(); |
| #if defined(_WIN32) |
| std::replace(file_name.begin(), file_name.end(), '\\', '/'); |
| #endif |
| if (file_name.find(layout_directory) == std::string::npos && |
| file_name.find(test_directory) == std::string::npos) |
| return; |
| |
| MatchFinder match_finder; |
| DiagnosticsReporter diagnostics(compiler_); |
| |
| LayoutObjectMethodMatcher layout_object_method_matcher(diagnostics); |
| layout_object_method_matcher.Register(match_finder); |
| |
| match_finder.matchAST(ast_context); |
| } |
| |
| } // namespace chrome_checker |