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