// 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 "RawPtrHelpers.h"
#include "StackAllocatedChecker.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "llvm/Support/LineIterator.h"
#include "llvm/Support/MemoryBuffer.h"
using namespace clang::ast_matchers;
namespace raw_ptr_plugin {
FilterFile::FilterFile(const std::vector<std::string>& lines) {
for (const auto& line : lines) {
file_lines_.insert(line);
}
}
bool FilterFile::ContainsLine(llvm::StringRef line) const {
auto it = file_lines_.find(line);
return it != file_lines_.end();
}
bool FilterFile::ContainsSubstringOf(llvm::StringRef string_to_match) const {
if (!inclusion_substring_regex_.has_value()) {
std::vector<std::string> regex_escaped_inclusion_file_lines;
std::vector<std::string> regex_escaped_exclusion_file_lines;
regex_escaped_inclusion_file_lines.reserve(file_lines_.size());
for (const llvm::StringRef& file_line : file_lines_.keys()) {
if (file_line.starts_with("!")) {
regex_escaped_exclusion_file_lines.push_back(
llvm::Regex::escape(file_line.substr(1)));
} else {
regex_escaped_inclusion_file_lines.push_back(
llvm::Regex::escape(file_line));
}
}
std::string inclusion_substring_regex_pattern =
llvm::join(regex_escaped_inclusion_file_lines.begin(),
regex_escaped_inclusion_file_lines.end(), "|");
inclusion_substring_regex_.emplace(inclusion_substring_regex_pattern);
std::string exclusion_substring_regex_pattern =
llvm::join(regex_escaped_exclusion_file_lines.begin(),
regex_escaped_exclusion_file_lines.end(), "|");
exclusion_substring_regex_.emplace(exclusion_substring_regex_pattern);
}
return inclusion_substring_regex_->match(string_to_match) &&
!exclusion_substring_regex_->match(string_to_match);
}
void FilterFile::ParseInputFile(const std::string& filepath,
const std::string& arg_name) {
if (filepath.empty()) {
return;
}
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> file_or_err =
llvm::MemoryBuffer::getFile(filepath);
if (std::error_code err = file_or_err.getError()) {
llvm::errs() << "ERROR: Cannot open the file specified in --" << arg_name
<< " argument: " << filepath << ": " << err.message() << "\n";
assert(false);
return;
}
llvm::line_iterator it(**file_or_err, true /* SkipBlanks */, '#');
for (; !it.is_at_eof(); ++it) {
llvm::StringRef line = *it;
// Remove trailing location information.
size_t loc_info_start_pos = line.find('@');
if (loc_info_start_pos != llvm::StringRef::npos) {
line = line.substr(0, loc_info_start_pos);
} else {
// Remove trailing comments.
size_t comment_start_pos = line.find('#');
if (comment_start_pos != llvm::StringRef::npos) {
line = line.substr(0, comment_start_pos);
}
}
line = line.trim();
if (line.empty()) {
continue;
}
file_lines_.insert(line);
}
}
clang::ast_matchers::internal::Matcher<clang::Decl> ImplicitFieldDeclaration() {
auto implicit_class_specialization_matcher =
classTemplateSpecializationDecl(isImplicitClassTemplateSpecialization());
auto implicit_function_specialization_matcher =
functionDecl(isImplicitFunctionTemplateSpecialization());
auto implicit_field_decl_matcher = fieldDecl(hasParent(cxxRecordDecl(anyOf(
isLambda(), implicit_class_specialization_matcher,
hasAncestor(decl(anyOf(implicit_class_specialization_matcher,
implicit_function_specialization_matcher)))))));
return implicit_field_decl_matcher;
}
clang::ast_matchers::internal::Matcher<clang::QualType> StackAllocatedQualType(
const raw_ptr_plugin::StackAllocatedPredicate* checker) {
return qualType(recordType(hasDeclaration(
cxxRecordDecl(isStackAllocated(*checker)))))
.bind("pointeeQualType");
}
// These represent the common conditions to skip the rewrite for reference and
// pointer decls. This includes decls that are:
// - listed in the --exclude-fields cmdline param or located in paths
// matched by --exclude-paths cmdline param
// - "implicit" (i.e. field decls that are not explicitly present in
// the source code)
// - located in Extern C context, in generated code or annotated with
// RAW_PTR_EXCLUSION
// - located under third_party/ except under third_party/blink as Blink
// is part of chromium git repo.
//
// Additionally, if |options.should_exclude_stack_allocated_records|,
// - Pointer pointing to a STACK_ALLOCATED() object.
// - Pointer that are a member of STACK_ALLOCATED() object.
// struct Foo {
// STACK_ALLOCATED();
// int* ptr2; // isDeclaredInStackAllocated(...)
// }
// struct Bar {
// Foo* ptr2; // hasDescendant(StackAllocatedQualType(...))
// }
clang::ast_matchers::internal::Matcher<clang::NamedDecl> PtrAndRefExclusions(
const RawPtrAndRefExclusionsOptions& options) {
if (!options.should_exclude_stack_allocated_records) {
return anyOf(isSpellingInSystemHeader(), isInExternCContext(),
isRawPtrExclusionAnnotated(), isInThirdPartyLocation(),
isInGeneratedLocation(), isNotSpelledInSource(),
isInLocationListedInFilterFile(options.paths_to_exclude),
isFieldDeclListedInFilterFile(options.fields_to_exclude),
ImplicitFieldDeclaration(), isObjCSynthesize());
} else {
return anyOf(
isSpellingInSystemHeader(), isInExternCContext(),
isRawPtrExclusionAnnotated(), isInThirdPartyLocation(),
isInGeneratedLocation(), isNotSpelledInSource(),
isInLocationListedInFilterFile(options.paths_to_exclude),
isFieldDeclListedInFilterFile(options.fields_to_exclude),
ImplicitFieldDeclaration(), isObjCSynthesize(),
hasDescendant(
StackAllocatedQualType(options.stack_allocated_predicate)),
isDeclaredInStackAllocated(*options.stack_allocated_predicate));
}
}
// These represent the common conditions to skip the check on existing
// |raw_ptr<T>| and |raw_ref<T>|. This includes decls that are:
// - located in system headers.
// - located under third_party/ except under third_party/blink as Blink
// is part of chromium git repo.
clang::ast_matchers::internal::Matcher<clang::TypeLoc>
PtrAndRefTypeLocExclusions() {
return anyOf(isSpellingInSystemHeader(), isInThirdPartyLocation());
}
// Unsupported pointer types =========
// Example:
// struct MyStruct {
// int (*func_ptr)();
// int (MyStruct::* member_func_ptr)(char);
// int (*ptr_to_array_of_ints)[123];
// };
// The above pointer types are not supported for the rewrite.
static const auto unsupported_pointee_types =
pointee(hasUnqualifiedDesugaredType(
anyOf(functionType(), memberPointerType(), arrayType())));
clang::ast_matchers::internal::Matcher<clang::Type> supported_pointer_type() {
return pointerType(unless(unsupported_pointee_types));
}
clang::ast_matchers::internal::Matcher<clang::Type> const_char_pointer_type(
bool should_rewrite_non_string_literals) {
if (should_rewrite_non_string_literals) {
return pointerType(pointee(qualType(hasCanonicalType(
anyOf(asString("const char"), asString("const wchar_t"),
asString("const char8_t"), asString("const char16_t"),
asString("const char32_t"))))));
}
return pointerType(pointee(qualType(
allOf(isConstQualified(), hasUnqualifiedDesugaredType(anyCharType())))));
}
clang::ast_matchers::internal::Matcher<clang::Decl> AffectedRawPtrFieldDecl(
const RawPtrAndRefExclusionsOptions& options) {
// TODO(crbug.com/40245402): Skipping const char pointers as it likely points
// to string literals where raw_ptr isn't necessary. Remove when we have
// implement const char support.
auto const_char_pointer_matcher = fieldDecl(hasType(
const_char_pointer_type(options.should_rewrite_non_string_literals)));
auto field_decl_matcher =
fieldDecl(allOf(hasType(supported_pointer_type()),
unless(anyOf(const_char_pointer_matcher,
PtrAndRefExclusions(options)))))
.bind("affectedFieldDecl");
return field_decl_matcher;
}
clang::ast_matchers::internal::Matcher<clang::Decl> AffectedRawRefFieldDecl(
const RawPtrAndRefExclusionsOptions& options) {
// Supported reference types =========
// Given
// struct MyStruct {
// int& int_ref;
// int i;
// int (&func_ref)();
// int (&ref_to_array_of_ints)[123];
// };
// matches |int&|, but not the other types.
auto supported_ref_types_matcher =
referenceType(unless(unsupported_pointee_types));
// Field declarations =========
// Given
// struct S {
// int& y;
// };
// matches |int& y|. Doesn't match:
// - non-reference types
// - fields matching criteria elaborated in PtrAndRefExclusions
auto field_decl_matcher =
fieldDecl(allOf(has(referenceTypeLoc().bind("affectedFieldDeclType")),
hasType(supported_ref_types_matcher),
unless(PtrAndRefExclusions(options))))
.bind("affectedFieldDecl");
return field_decl_matcher;
}
clang::ast_matchers::internal::Matcher<clang::TypeLoc>
RawPtrToStackAllocatedTypeLoc(
const raw_ptr_plugin::StackAllocatedPredicate* predicate) {
// Given
// class StackAllocatedType { STACK_ALLOCATED(); };
// class StackAllocatedSubType : public StackAllocatedType {};
// class NonStackAllocatedType {};
//
// struct MyStruct {
// raw_ptr<StackAllocatedType> a;
// raw_ptr<StackAllocatedSubType> b;
// raw_ptr<NonStackAllocatedType> c;
// raw_ptr<some_container<StackAllocatedType>> d;
// raw_ptr<some_container<StackAllocatedSubType>> e;
// raw_ptr<some_container<NonStackAllocatedType>> f;
// some_container<raw_ptr<StackAllocatedType>> g;
// some_container<raw_ptr<StackAllocatedSubType>> h;
// some_container<raw_ptr<NonStackAllocatedType>> i;
// };
// matches fields a,b,d,e,g,h, and not c,f,i.
// Similarly, given
// void my_func() {
// raw_ptr<StackAllocatedType> a;
// raw_ptr<StackAllocatedSubType> b;
// raw_ptr<NonStackAllocatedType> c;
// raw_ptr<some_container<StackAllocatedType>> d;
// raw_ptr<some_container<StackAllocatedSubType>> e;
// raw_ptr<some_container<NonStackAllocatedType>> f;
// some_container<raw_ptr<StackAllocatedType>> g;
// some_container<raw_ptr<StackAllocatedSubType>> h;
// some_container<raw_ptr<NonStackAllocatedType>> i;
// }
// matches variables a,b,d,e,g,h, and not c,f,i.
// Matches records |raw_ptr| or |raw_ref|.
auto pointer_record =
cxxRecordDecl(hasAnyName("base::raw_ptr", "base::raw_ref"))
.bind("pointerRecordDecl");
// Matches qual types having a record with |isStackAllocated| = true.
auto pointee_type =
qualType(StackAllocatedQualType(predicate)).bind("pointeeQualType");
// Matches type locs like |raw_ptr<StackAllocatedType>| or
// |raw_ref<StackAllocatedType>|.
auto stack_allocated_rawptr_type_loc =
templateSpecializationTypeLoc(
allOf(unless(PtrAndRefTypeLocExclusions()),
loc(templateSpecializationType(hasDeclaration(
allOf(pointer_record,
classTemplateSpecializationDecl(hasTemplateArgument(
0, refersToType(pointee_type)))))))))
.bind("stackAllocatedRawPtrTypeLoc");
return stack_allocated_rawptr_type_loc;
}
clang::ast_matchers::internal::Matcher<clang::Stmt> BadRawPtrCastExpr(
const CastingUnsafePredicate& casting_unsafe_predicate,
const FilterFile& exclude_files,
const FilterFile& exclude_functions) {
// 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(clang::CK_BitCast),
hasCastKind(clang::CK_LValueBitCast),
hasCastKind(clang::CK_LValueToRValueBitCast),
hasCastKind(clang::CK_PointerToIntegral),
hasCastKind(clang::CK_IntegralToPointer)));
// Matches implicit casts happening in invocation inside template context.
// void f(int v);
// void f(void* p);
// template <typename T>
// void call_f(T t) { f(t); }
// ^ implicit cast here if |T| = |int*|
// We exclude this cast from check because we cannot apply
// |base::unsafe_raw_ptr_*_cast<void*>(t)| here.
auto in_template_invocation_ctx = implicitCastExpr(
allOf(isInTemplateInstantiation(), hasParent(invocation())));
// Matches implicit casts happening in comparison.
// int* x;
// void* y;
// if (x < y) f();
// ^~~~~ |x| is implicit casted into |void*| here
// This cast is guaranteed to be safe because it cannot break ref count.
auto in_comparison_ctx =
implicitCastExpr(hasParent(binaryOperator(isComparisonOperator())));
// Matches implicit casts happening in invocation to allow-listed
// declarations.
auto in_allowlisted_invocation_ctx =
implicitCastExpr(hasParent(invocation(hasDeclaration(
namedDecl(isFieldDeclListedInFilterFile(&exclude_functions))))));
// Matches casts to const pointer types pointing to built-in types.
// e.g. matches |const char*| and |const void*| but neither |const int**| nor
// |int* const*|.
// They are safe as long as const qualifier is kept because const means we
// shouldn't be writing to the memory and won't mutate the value in a way that
// causes BRP's refcount inconsistency.
auto const_builtin_pointer_type =
type(hasUnqualifiedDesugaredType(pointerType(
pointee(qualType(allOf(isConstQualified(), builtinType()))))));
auto cast_expr_to_const_pointer = anyOf(
implicitCastExpr(hasImplicitDestinationType(const_builtin_pointer_type)),
explicitCastExpr(hasDestinationType(const_builtin_pointer_type)));
// Unsafe castings are allowed if:
// - In locations developers have no control
// - In system headers
// - In third party libraries
// - In non-source locations (e.g. <scratch space>)
// - In separate repository locations (e.g. //internal)
// - In locations that are likely to be safe
// - In pointer comparison context
// - In allowlisted function/constructor invocations
// - To const-qualified void/char pointers
// - In cases that the cast is indispensable and developers can guarantee it
// will not break BRP's refcount
// - In |base::unsafe_raw_ptr_static_cast<T>(...)|
// - In |base::unsafe_raw_ptr_reinterpret_cast<T>(...)|
// - In |base::unsafe_raw_ptr_bit_cast<T>(...)|
// - In cases that the cast is indispensable but developers cannot use the
// cast exclusion listed above
// - Implicit casts inside template context as there can be multiple
// destination types depending on how template is instantiated
auto exclusions =
anyOf(isSpellingInSystemHeader(), isInThirdPartyLocation(),
isNotSpelledInSource(),
isInLocationListedInFilterFile(&exclude_files), in_comparison_ctx,
in_allowlisted_invocation_ctx, cast_expr_to_const_pointer,
isInRawPtrCastHeader(), in_template_invocation_ctx);
// To correctly display the error location, bind enclosing castExpr if
// available.
auto enclosingCastExpr = hasEnclosingExplicitCastExpr(
explicitCastExpr().bind("enclosingCastExpr"));
// Implicit/explicit casting from/to |raw_ptr<T>| matches.
// Both casting direction is unsafe.
// https://godbolt.org/z/zqKMzcKfo
// |__bit/bit_cast.h| header is configured to bypass exclusions to perform
// checking on |std::bit_cast<T>|.
auto cast_matcher =
castExpr(
allOf(anyOf(hasSourceExpression(hasType(src_type)),
implicitCastExpr(hasImplicitDestinationType(dst_type)),
explicitCastExpr(hasDestinationType(dst_type))),
cast_kind, optionally(enclosingCastExpr),
anyOf(isInStdBitCastHeader(), unless(exclusions))))
.bind("castExpr");
return cast_matcher;
}
// If |field_decl| declares a field in an implicit template specialization, then
// finds and returns the corresponding FieldDecl from the template definition.
// Otherwise, just returns the original |field_decl| argument.
const clang::FieldDecl* GetExplicitDecl(const clang::FieldDecl* field_decl) {
if (field_decl->isAnonymousStructOrUnion()) {
return field_decl; // Safe fallback - |field_decl| is not a pointer field.
}
const clang::CXXRecordDecl* record_decl =
clang::dyn_cast<clang::CXXRecordDecl>(field_decl->getParent());
if (!record_decl) {
return field_decl; // Non-C++ records are never template instantiations.
}
const clang::CXXRecordDecl* pattern_decl =
record_decl->getTemplateInstantiationPattern();
if (!pattern_decl) {
return field_decl; // |pattern_decl| is not a template instantiation.
}
if (record_decl->getTemplateSpecializationKind() !=
clang::TemplateSpecializationKind::TSK_ImplicitInstantiation) {
return field_decl; // |field_decl| was in an *explicit* specialization.
}
// Find the field decl with the same name in |pattern_decl|.
clang::DeclContextLookupResult lookup_result =
pattern_decl->lookup(field_decl->getDeclName());
assert(!lookup_result.empty());
const clang::NamedDecl* found_decl = lookup_result.front();
assert(found_decl);
field_decl = clang::dyn_cast<clang::FieldDecl>(found_decl);
assert(field_decl);
return field_decl;
}
// If |original_param| declares a parameter in an implicit template
// specialization of a function or method, then finds and returns the
// corresponding ParmVarDecl from the template definition. Otherwise, just
// returns the |original_param| argument.
//
// Note: nullptr may be returned in rare, unimplemented cases.
const clang::ParmVarDecl* GetExplicitDecl(
const clang::ParmVarDecl* original_param) {
const clang::FunctionDecl* original_func =
clang::dyn_cast<clang::FunctionDecl>(original_param->getDeclContext());
if (!original_func) {
// |!original_func| may happen when the ParmVarDecl is part of a
// FunctionType, but not part of a FunctionDecl:
// base::RepeatingCallback<void(int parm_var_decl_here)>
//
// In theory, |parm_var_decl_here| can also represent an implicit template
// specialization in this scenario. OTOH, it should be rare + shouldn't
// matter for this rewriter, so for now let's just return the
// |original_param|.
//
// TODO: Implement support for this scenario.
return nullptr;
}
const clang::FunctionDecl* pattern_func =
original_func->getTemplateInstantiationPattern();
if (!pattern_func) {
// |original_func| is not a template instantiation - return the
// |original_param|.
return original_param;
}
// See if |pattern_func| has a parameter that is a template parameter pack.
bool has_param_pack = false;
unsigned int index_of_param_pack = std::numeric_limits<unsigned int>::max();
for (unsigned int i = 0; i < pattern_func->getNumParams(); i++) {
const clang::ParmVarDecl* pattern_param = pattern_func->getParamDecl(i);
if (!pattern_param->isParameterPack()) {
continue;
}
if (has_param_pack) {
// TODO: Implement support for multiple parameter packs.
return nullptr;
}
has_param_pack = true;
index_of_param_pack = i;
}
// Find and return the corresponding ParmVarDecl from |pattern_func|.
unsigned int original_index = original_param->getFunctionScopeIndex();
unsigned int pattern_index = std::numeric_limits<unsigned int>::max();
if (!has_param_pack) {
pattern_index = original_index;
} else {
// |original_func| has parameters that look like this:
// l1, l2, l3, p1, p2, p3, t1, t2, t3
// where
// lN is a leading, non-pack parameter
// pN is an expansion of a template parameter pack
// tN is a trailing, non-pack parameter
// Using the knowledge above, let's adjust |pattern_index| as needed.
unsigned int leading_param_num = index_of_param_pack; // How many |lN|.
unsigned int pack_expansion_num = // How many |pN| above.
original_func->getNumParams() - pattern_func->getNumParams() + 1;
if (original_index < leading_param_num) {
// |original_param| is a leading, non-pack parameter.
pattern_index = original_index;
} else if (leading_param_num <= original_index &&
original_index < (leading_param_num + pack_expansion_num)) {
// |original_param| is an expansion of a template pack parameter.
pattern_index = index_of_param_pack;
} else if ((leading_param_num + pack_expansion_num) <= original_index) {
// |original_param| is a trailing, non-pack parameter.
pattern_index = original_index - pack_expansion_num + 1;
}
}
assert(pattern_index < pattern_func->getNumParams());
return pattern_func->getParamDecl(pattern_index);
}
} // namespace raw_ptr_plugin