// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "FindBadRawPtrPatterns.h"
#include <memory>
#include "RawPtrHelpers.h"
#include "RawPtrManualPathsToIgnore.h"
#include "SeparateRepositoryPaths.h"
#include "StackAllocatedChecker.h"
#include "TypePredicateUtil.h"
#include "Util.h"
#include "clang/AST/AST.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/Attr.h"
#include "clang/AST/CXXInheritance.h"
#include "clang/AST/TypeLoc.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "llvm/Support/TimeProfiler.h"
using namespace clang;
using namespace clang::ast_matchers;
namespace raw_ptr_plugin {
constexpr char kBadCastDiagnostic[] =
"[chromium-style] casting '%0' to '%1 is not allowed.";
constexpr char kBadCastDiagnosticNoteExplanation[] =
"[chromium-style] '%0' manages BackupRefPtr refcounts; bypassing its C++ "
"interface or treating it as a POD will lead to memory safety errors.";
constexpr char kBadCastDiagnosticNoteType[] =
"[chromium-style] '%0' manages BackupRefPtr or its container here.";
class BadCastMatcher : public MatchFinder::MatchCallback {
public:
explicit BadCastMatcher(clang::CompilerInstance& compiler,
const FilterFile& exclude_files,
const FilterFile& exclude_functions)
: compiler_(compiler),
exclude_files_(exclude_files),
exclude_functions_(exclude_functions) {
error_bad_cast_signature_ = compiler_.getDiagnostics().getCustomDiagID(
clang::DiagnosticsEngine::Error, kBadCastDiagnostic);
note_bad_cast_signature_explanation_ =
compiler_.getDiagnostics().getCustomDiagID(
clang::DiagnosticsEngine::Note, kBadCastDiagnosticNoteExplanation);
note_bad_cast_signature_type_ = compiler_.getDiagnostics().getCustomDiagID(
clang::DiagnosticsEngine::Note, kBadCastDiagnosticNoteType);
}
void Register(MatchFinder& match_finder) {
auto cast_matcher = BadRawPtrCastExpr(casting_unsafe_predicate_,
exclude_files_, exclude_functions_);
match_finder.addMatcher(cast_matcher, this);
}
void run(const MatchFinder::MatchResult& result) override {
const clang::CastExpr* cast_expr =
result.Nodes.getNodeAs<clang::CastExpr>("castExpr");
assert(cast_expr && "matcher should bind 'castExpr'");
const clang::SourceManager& source_manager = *result.SourceManager;
clang::SourceLocation loc = cast_expr->getSourceRange().getBegin();
std::string file_path =
GetFilename(source_manager, loc, FilenameLocationType::kSpellingLoc);
clang::PrintingPolicy printing_policy(result.Context->getLangOpts());
const std::string src_name =
cast_expr->getSubExpr()->getType().getAsString(printing_policy);
const std::string dst_name =
cast_expr->getType().getAsString(printing_policy);
const auto* src_type = result.Nodes.getNodeAs<clang::Type>("srcType");
const auto* dst_type = result.Nodes.getNodeAs<clang::Type>("dstType");
assert((src_type || dst_type) &&
"matcher should bind 'srcType' or 'dstType'");
const auto* enclosing_cast_expr =
result.Nodes.getNodeAs<clang::ExplicitCastExpr>("enclosingCastExpr");
const auto* cast_expr_for_display =
enclosing_cast_expr ? enclosing_cast_expr : cast_expr;
compiler_.getDiagnostics().Report(cast_expr_for_display->getEndLoc(),
error_bad_cast_signature_)
<< src_name << dst_name;
std::shared_ptr<MatchResult> type_note;
if (src_type != nullptr) {
compiler_.getDiagnostics().Report(cast_expr_for_display->getEndLoc(),
note_bad_cast_signature_explanation_)
<< src_name;
type_note = casting_unsafe_predicate_.GetMatchResult(src_type);
} else {
compiler_.getDiagnostics().Report(cast_expr_for_display->getEndLoc(),
note_bad_cast_signature_explanation_)
<< dst_name;
type_note = casting_unsafe_predicate_.GetMatchResult(dst_type);
}
while (type_note) {
if (type_note->source_loc()) {
const auto& type_name = clang::QualType::getAsString(
type_note->type(), {}, printing_policy);
compiler_.getDiagnostics().Report(*type_note->source_loc(),
note_bad_cast_signature_type_)
<< type_name;
}
type_note = type_note->source();
}
}
llvm::StringRef getID() const override { return "BadCastMatcher"; };
private:
clang::CompilerInstance& compiler_;
const FilterFile& exclude_files_;
const FilterFile& exclude_functions_;
CastingUnsafePredicate casting_unsafe_predicate_;
unsigned error_bad_cast_signature_;
unsigned note_bad_cast_signature_explanation_;
unsigned note_bad_cast_signature_type_;
};
const char kNeedRawPtrSignature[] =
"[chromium-rawptr] Use raw_ptr<T> instead of a raw pointer.";
class RawPtrFieldMatcher : public MatchFinder::MatchCallback {
public:
explicit RawPtrFieldMatcher(
clang::CompilerInstance& compiler,
const RawPtrAndRefExclusionsOptions& exclusion_options)
: compiler_(compiler), exclusion_options_(exclusion_options) {
error_need_raw_ptr_signature_ = compiler_.getDiagnostics().getCustomDiagID(
clang::DiagnosticsEngine::Error, kNeedRawPtrSignature);
}
void Register(MatchFinder& match_finder) {
auto field_decl_matcher = AffectedRawPtrFieldDecl(exclusion_options_);
match_finder.addMatcher(field_decl_matcher, this);
}
void run(const MatchFinder::MatchResult& result) override {
const clang::FieldDecl* field_decl =
result.Nodes.getNodeAs<clang::FieldDecl>("affectedFieldDecl");
assert(field_decl && "matcher should bind 'fieldDecl'");
const clang::TypeSourceInfo* type_source_info =
field_decl->getTypeSourceInfo();
assert(type_source_info && "assuming |type_source_info| is always present");
assert(type_source_info->getType()->isPointerType() &&
"matcher should only match pointer types");
compiler_.getDiagnostics().Report(field_decl->getLocation(),
error_need_raw_ptr_signature_);
}
llvm::StringRef getID() const override { return "RawPtrFieldMatcher"; };
private:
clang::CompilerInstance& compiler_;
unsigned error_need_raw_ptr_signature_;
const RawPtrAndRefExclusionsOptions& exclusion_options_;
};
const char kNeedRawRefSignature[] =
"[chromium-rawref] Use raw_ref<T> instead of a native reference.";
class RawRefFieldMatcher : public MatchFinder::MatchCallback {
public:
explicit RawRefFieldMatcher(
clang::CompilerInstance& compiler,
const RawPtrAndRefExclusionsOptions& exclusion_options)
: compiler_(compiler), exclusion_options_(exclusion_options) {
error_need_raw_ref_signature_ = compiler_.getDiagnostics().getCustomDiagID(
clang::DiagnosticsEngine::Error, kNeedRawRefSignature);
}
void Register(MatchFinder& match_finder) {
auto field_decl_matcher = AffectedRawRefFieldDecl(exclusion_options_);
match_finder.addMatcher(field_decl_matcher, this);
}
void run(const MatchFinder::MatchResult& result) override {
const clang::FieldDecl* field_decl =
result.Nodes.getNodeAs<clang::FieldDecl>("affectedFieldDecl");
assert(field_decl && "matcher should bind 'fieldDecl'");
const clang::TypeSourceInfo* type_source_info =
field_decl->getTypeSourceInfo();
assert(type_source_info && "assuming |type_source_info| is always present");
assert(type_source_info->getType()->isReferenceType() &&
"matcher should only match reference types");
compiler_.getDiagnostics().Report(field_decl->getEndLoc(),
error_need_raw_ref_signature_);
}
llvm::StringRef getID() const override { return "RawRefFieldMatcher"; };
private:
clang::CompilerInstance& compiler_;
unsigned error_need_raw_ref_signature_;
const RawPtrAndRefExclusionsOptions exclusion_options_;
};
const char kNoRawPtrToStackAllocatedSignature[] =
"[chromium-raw-ptr-to-stack-allocated] Do not use '%0<T>' on a "
"`STACK_ALLOCATED` object '%1'.";
class RawPtrToStackAllocatedMatcher : public MatchFinder::MatchCallback {
public:
explicit RawPtrToStackAllocatedMatcher(clang::CompilerInstance& compiler)
: compiler_(compiler), stack_allocated_predicate_() {
error_no_raw_ptr_to_stack_ = compiler_.getDiagnostics().getCustomDiagID(
clang::DiagnosticsEngine::Error, kNoRawPtrToStackAllocatedSignature);
}
void Register(MatchFinder& match_finder) {
auto value_decl_matcher =
RawPtrToStackAllocatedTypeLoc(&stack_allocated_predicate_);
match_finder.addMatcher(value_decl_matcher, this);
}
void run(const MatchFinder::MatchResult& result) override {
const auto* pointer =
result.Nodes.getNodeAs<clang::CXXRecordDecl>("pointerRecordDecl");
assert(pointer && "matcher should bind 'pointerRecordDecl'");
const auto* pointee =
result.Nodes.getNodeAs<clang::QualType>("pointeeQualType");
assert(pointee && "matcher should bind 'pointeeQualType'");
clang::PrintingPolicy printing_policy(result.Context->getLangOpts());
const std::string pointee_name = pointee->getAsString(printing_policy);
const auto* type_loc =
result.Nodes.getNodeAs<clang::TypeLoc>("stackAllocatedRawPtrTypeLoc");
assert(type_loc && "matcher should bind 'stackAllocatedRawPtrTypeLoc'");
compiler_.getDiagnostics().Report(type_loc->getEndLoc(),
error_no_raw_ptr_to_stack_)
<< pointer->getNameAsString() << pointee_name;
}
llvm::StringRef getID() const override {
return "RawPtrToStackAllocatedMatcher";
};
private:
clang::CompilerInstance& compiler_;
StackAllocatedPredicate stack_allocated_predicate_;
unsigned error_no_raw_ptr_to_stack_;
};
const char kNeedRawSpanSignature[] =
"[chromium-rawptr] Use raw_span<T> instead of a span<T>.";
const char kNeedContainerSpanSignature[] =
"[chromium-rawptr] Use raw_span<T> instead of a span<T> in the field "
"type's template arguments.";
class SpanFieldMatcher : public MatchFinder::MatchCallback {
public:
explicit SpanFieldMatcher(
clang::CompilerInstance& compiler,
const RawPtrAndRefExclusionsOptions& exclusion_options)
: compiler_(compiler), exclusion_options_(exclusion_options) {
error_need_span_signature_ = compiler_.getDiagnostics().getCustomDiagID(
clang::DiagnosticsEngine::Error, kNeedRawSpanSignature);
error_need_container_span_signature_ =
compiler_.getDiagnostics().getCustomDiagID(
clang::DiagnosticsEngine::Error, kNeedContainerSpanSignature);
}
void Register(MatchFinder& match_finder) {
auto raw_span = hasTemplateArgument(
2, refersToType(qualType(hasCanonicalType(qualType(hasDeclaration(
mapAnyOf(classTemplateSpecializationDecl, classTemplateDecl)
.with(hasName("raw_ptr"))))))));
auto string_literals_span = hasTemplateArgument(
0, refersToType(qualType(hasCanonicalType(
anyOf(asString("const char"), asString("const wchar_t"),
asString("const char8_t"), asString("const char16_t"),
asString("const char32_t"))))));
auto excluded_spans = anyOf(raw_span, string_literals_span);
auto span_type = anyOf(
qualType(hasCanonicalType(
qualType(hasDeclaration(classTemplateSpecializationDecl(
hasName("base::span"), unless(excluded_spans)))))),
qualType(hasCanonicalType(qualType(type(templateSpecializationType(
hasDeclaration(classTemplateDecl(hasName("base::span"))),
unless(excluded_spans)))))));
auto optional_span_type = anyOf(
qualType(
hasCanonicalType(hasDeclaration(classTemplateSpecializationDecl(
hasName("optional"),
hasTemplateArgument(0, refersToType(span_type)))))),
qualType(hasCanonicalType(qualType(type(templateSpecializationType(
hasDeclaration(classTemplateDecl(hasName("optional"))),
hasAnyTemplateArgument(refersToType(span_type))))))));
auto container_methods =
anyOf(allOf(hasMethod(hasName("push_back")),
hasMethod(hasName("pop_back")), hasMethod(hasName("size"))),
allOf(hasMethod(hasName("insert")), hasMethod(hasName("erase")),
hasMethod(hasName("size"))),
allOf(hasMethod(hasName("push")), hasMethod(hasName("pop")),
hasMethod(hasName("size"))));
auto template_argument =
templateArgument(refersToType(anyOf(span_type, optional_span_type)));
auto template_arguments = anyOf(hasTemplateArgument(0, template_argument),
hasTemplateArgument(1, template_argument));
auto container_of_span_type =
qualType(hasCanonicalType(anyOf(
qualType(hasDeclaration(classTemplateSpecializationDecl(
container_methods, template_arguments))),
qualType(type(templateSpecializationType(
hasDeclaration(classTemplateDecl(
has(cxxRecordDecl(container_methods)))),
template_arguments))))))
.bind("container_type");
auto field_decl_matcher =
traverse(clang::TK_IgnoreUnlessSpelledInSource,
fieldDecl(hasType(qualType(anyOf(span_type, optional_span_type,
container_of_span_type))),
unless(PtrAndRefExclusions(exclusion_options_)))
.bind("affectedFieldDecl"));
match_finder.addMatcher(field_decl_matcher, this);
}
void run(const MatchFinder::MatchResult& result) override {
const clang::FieldDecl* field_decl =
result.Nodes.getNodeAs<clang::FieldDecl>("affectedFieldDecl");
assert(field_decl && "matcher should bind 'fieldDecl'");
if (result.Nodes.getNodeAs<clang::QualType>("container_type")) {
compiler_.getDiagnostics().Report(field_decl->getLocation(),
error_need_container_span_signature_);
} else {
compiler_.getDiagnostics().Report(field_decl->getLocation(),
error_need_span_signature_);
}
}
llvm::StringRef getID() const override { return "SpanFieldMatcher"; };
private:
clang::CompilerInstance& compiler_;
unsigned error_need_span_signature_;
unsigned error_need_container_span_signature_;
const RawPtrAndRefExclusionsOptions& exclusion_options_;
};
void FindBadRawPtrPatterns(const Options& options,
clang::ASTContext& ast_context,
clang::CompilerInstance& compiler) {
llvm::StringMap<llvm::TimeRecord> Records;
MatchFinder::MatchFinderOptions FinderOptions;
if (options.enable_match_profiling) {
FinderOptions.CheckProfiling.emplace(Records);
}
MatchFinder match_finder(std::move(FinderOptions));
std::vector<std::string> paths_to_exclude_lines;
std::vector<std::string> check_bad_raw_ptr_cast_exclude_paths;
for (auto* const line : kRawPtrManualPathsToIgnore) {
paths_to_exclude_lines.push_back(line);
}
for (auto* const line : kSeparateRepositoryPaths) {
paths_to_exclude_lines.push_back(line);
check_bad_raw_ptr_cast_exclude_paths.push_back(line);
}
paths_to_exclude_lines.insert(paths_to_exclude_lines.end(),
options.raw_ptr_paths_to_exclude_lines.begin(),
options.raw_ptr_paths_to_exclude_lines.end());
check_bad_raw_ptr_cast_exclude_paths.insert(
check_bad_raw_ptr_cast_exclude_paths.end(),
options.check_bad_raw_ptr_cast_exclude_paths.begin(),
options.check_bad_raw_ptr_cast_exclude_paths.end());
FilterFile exclude_fields(options.exclude_fields_file, "exclude-fields");
FilterFile exclude_lines(paths_to_exclude_lines);
StackAllocatedPredicate stack_allocated_predicate;
RawPtrAndRefExclusionsOptions exclusion_options{
&exclude_fields, &exclude_lines, options.check_raw_ptr_to_stack_allocated,
&stack_allocated_predicate, options.check_ptrs_to_non_string_literals};
FilterFile filter_check_bad_raw_ptr_cast_exclude_paths(
check_bad_raw_ptr_cast_exclude_paths);
FilterFile filter_check_bad_raw_ptr_cast_exclude_funcs(
options.check_bad_raw_ptr_cast_exclude_funcs);
BadCastMatcher bad_cast_matcher(compiler,
filter_check_bad_raw_ptr_cast_exclude_paths,
filter_check_bad_raw_ptr_cast_exclude_funcs);
if (options.check_bad_raw_ptr_cast) {
bad_cast_matcher.Register(match_finder);
}
RawPtrFieldMatcher field_matcher(compiler, exclusion_options);
if (options.check_raw_ptr_fields) {
field_matcher.Register(match_finder);
}
RawRefFieldMatcher ref_field_matcher(compiler, exclusion_options);
if (options.check_raw_ref_fields) {
ref_field_matcher.Register(match_finder);
}
RawPtrToStackAllocatedMatcher raw_ptr_to_stack(compiler);
if (options.check_raw_ptr_to_stack_allocated &&
!options.disable_check_raw_ptr_to_stack_allocated_error) {
raw_ptr_to_stack.Register(match_finder);
}
SpanFieldMatcher raw_span_matcher(compiler, exclusion_options);
if (options.check_span_fields) {
raw_span_matcher.Register(match_finder);
}
{
llvm::TimeTraceScope TimeScope(
"match_finder.matchAST in FindBadRawPtrPatterns");
match_finder.matchAST(ast_context);
}
if (options.enable_match_profiling) {
llvm::TimerGroup TG("FindBadRawPtrPatterns",
"FindBadRawPtrPatterns match profiling", Records);
TG.print(llvm::errs());
}
}
} // namespace raw_ptr_plugin