chromium/tools/clang/plugins/CheckLayoutObjectMethodsVisitor.cpp

// 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