blob: ed14b9920b8a17998c249db0f41c923be7bd2789 [file] [log] [blame]
// 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 "StackAllocatedChecker.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"
using namespace clang;
using namespace clang::ast_matchers;
namespace chrome_checker {
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)
: compiler_(compiler) {
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) {
// Matches anything contains |raw_ptr<T>| / |raw_ref<T>|.
auto src_type =
type(isCastingUnsafe(casting_unsafe_predicate_)).bind("srcType");
auto dst_type =
type(isCastingUnsafe(casting_unsafe_predicate_)).bind("dstType");
// Matches |static_cast| on pointers, all |bit_cast|
// and all |reinterpret_cast|.
auto cast_kind = castExpr(anyOf(
hasCastKind(CK_BitCast), hasCastKind(CK_LValueBitCast),
hasCastKind(CK_LValueToRValueBitCast),
hasCastKind(CK_PointerToIntegral), hasCastKind(CK_IntegralToPointer)));
// Implicit/explicit casting from/to |raw_ptr<T>| matches.
// Both casting direction is unsafe.
// https://godbolt.org/z/zqKMzcKfo
auto cast_matcher =
castExpr(
allOf(anyOf(hasSourceExpression(hasType(src_type)),
implicitCastExpr(hasImplicitDestinationType(dst_type)),
explicitCastExpr(hasDestinationType(dst_type))),
cast_kind))
.bind("castExpr");
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);
// Using raw_ptr<T> in a stdlib collection will cause a cast.
// e.g.
// https://source.chromium.org/chromium/chromium/src/+/main:components/feed/core/v2/xsurface_datastore.h;drc=a0ff03edcace35ec020edd235f4d9e9735fc9690;l=107
// |__bit/bit_cast.h| header is excluded to perform checking on
// |std::bit_cast<T>|.
if (file_path.find("buildtools/third_party/libc++") != std::string::npos &&
file_path.find("__bit/bit_cast.h") == std::string::npos) {
return;
}
// Exclude casts via "unsafe_raw_ptr_*_cast".
if (file_path.find(
"base/allocator/partition_allocator/pointers/raw_ptr_cast.h") !=
std::string::npos) {
return;
}
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'");
compiler_.getDiagnostics().Report(cast_expr->getEndLoc(),
error_bad_cast_signature_)
<< src_name << dst_name;
std::shared_ptr<CastingSafety> type_note;
if (src_type != nullptr) {
compiler_.getDiagnostics().Report(cast_expr->getEndLoc(),
note_bad_cast_signature_explanation_)
<< src_name;
type_note = casting_unsafe_predicate_.GetCastingSafety(src_type);
} else {
compiler_.getDiagnostics().Report(cast_expr->getEndLoc(),
note_bad_cast_signature_explanation_)
<< dst_name;
type_note = casting_unsafe_predicate_.GetCastingSafety(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();
}
}
private:
clang::CompilerInstance& compiler_;
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->getEndLoc(),
error_need_raw_ptr_signature_);
}
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_);
}
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;
}
private:
clang::CompilerInstance& compiler_;
StackAllocatedPredicate stack_allocated_predicate_;
unsigned error_no_raw_ptr_to_stack_;
};
void FindBadRawPtrPatterns(Options options,
clang::ASTContext& ast_context,
clang::CompilerInstance& compiler) {
MatchFinder match_finder;
BadCastMatcher bad_cast_matcher(compiler);
if (options.check_bad_raw_ptr_cast)
bad_cast_matcher.Register(match_finder);
std::vector<std::string> paths_to_exclude_lines;
for (auto* const line : kRawPtrManualPathsToIgnore) {
paths_to_exclude_lines.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());
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};
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) {
raw_ptr_to_stack.Register(match_finder);
}
match_finder.matchAST(ast_context);
}
} // namespace chrome_checker