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