#include "BadPatternFinder.h"
#include <clang/AST/Decl.h>
#include <clang/AST/RecordLayout.h>
#include <algorithm>
#include "BlinkGCPluginOptions.h"
#include "DiagnosticsReporter.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchersMacros.h"
using namespace clang::ast_matchers;
namespace {
TypeMatcher GarbageCollectedType() {
auto has_gc_base = hasCanonicalType(hasDeclaration(
cxxRecordDecl(isDerivedFrom(hasAnyName("::cppgc::GarbageCollected",
"::cppgc::GarbageCollectedMixin")))
.bind("gctype")));
return anyOf(has_gc_base,
hasCanonicalType(arrayType(hasElementType(has_gc_base))));
}
TypeMatcher MemberType() {
auto has_member_base = hasCanonicalType(hasDeclaration(
classTemplateSpecializationDecl(
hasName("::cppgc::internal::BasicMember"),
hasAnyTemplateArgument(
refersToType(hasCanonicalType(hasDeclaration(anyOf(
cxxRecordDecl(hasName("::cppgc::internal::StrongMemberTag")),
cxxRecordDecl(
hasName("::cppgc::internal::WeakMemberTag"))))))))
.bind("member")));
return anyOf(has_member_base,
hasCanonicalType(arrayType(hasElementType(has_member_base))));
}
TypeMatcher TraceableType() {
auto has_gc_base = hasCanonicalType(hasDeclaration(
cxxRecordDecl(
hasMethod(cxxMethodDecl(
hasName("Trace"), isConst(), parameterCountIs(1),
hasParameter(
0, parmVarDecl(hasType(pointerType(pointee(hasCanonicalType(
hasDeclaration(cxxRecordDecl(isSameOrDerivedFrom(
hasName("cppgc::Visitor")))))))))))))
.bind("traceable")));
return anyOf(has_gc_base,
hasCanonicalType(arrayType(hasElementType(has_gc_base))));
}
class UniquePtrGarbageCollectedMatcher : public MatchFinder::MatchCallback {
public:
explicit UniquePtrGarbageCollectedMatcher(DiagnosticsReporter& diagnostics)
: diagnostics_(diagnostics) {}
void Register(MatchFinder& match_finder) {
auto make_unique_matcher =
callExpr(
callee(functionDecl(
hasAnyName("::std::make_unique", "::base::WrapUnique"),
hasTemplateArgument(
0, refersToType(GarbageCollectedType())))
.bind("badfunc")))
.bind("bad");
match_finder.addDynamicMatcher(make_unique_matcher, this);
}
void run(const MatchFinder::MatchResult& result) override {
auto* bad_use = result.Nodes.getNodeAs<clang::Expr>("bad");
auto* bad_function = result.Nodes.getNodeAs<clang::FunctionDecl>("badfunc");
auto* gc_type = result.Nodes.getNodeAs<clang::CXXRecordDecl>("gctype");
diagnostics_.UniquePtrUsedWithGC(bad_use, bad_function, gc_type);
}
private:
DiagnosticsReporter& diagnostics_;
};
bool IsOnStack(const clang::Decl* decl, RecordCache& record_cache) {
if (dyn_cast<const clang::VarDecl>(decl)) {
return true;
}
const clang::FieldDecl* field_decl = dyn_cast<const clang::FieldDecl>(decl);
assert(field_decl);
const clang::CXXRecordDecl* parent_decl =
dyn_cast<const clang::CXXRecordDecl>(field_decl->getParent());
assert(parent_decl);
return record_cache.Lookup(parent_decl)->IsStackAllocated();
}
class OptionalOrRawPtrToGCedMatcher : public MatchFinder::MatchCallback {
public:
OptionalOrRawPtrToGCedMatcher(DiagnosticsReporter& diagnostics,
RecordCache& record_cache)
: diagnostics_(diagnostics), record_cache_(record_cache) {}
void Register(MatchFinder& match_finder) {
auto optional_gced_type = hasType(
classTemplateSpecializationDecl(
hasAnyName("::absl::optional", "::std::optional", "::base::raw_ptr",
"::base::raw_ref"),
hasTemplateArgument(0, refersToType(anyOf(GarbageCollectedType(),
TraceableType()))))
.bind("type"));
auto optional_field = fieldDecl(optional_gced_type).bind("bad_decl");
auto optional_var = varDecl(optional_gced_type).bind("bad_decl");
auto optional_new_expression =
cxxNewExpr(has(cxxConstructExpr(optional_gced_type))).bind("bad_new");
match_finder.addDynamicMatcher(optional_field, this);
match_finder.addDynamicMatcher(optional_var, this);
match_finder.addDynamicMatcher(optional_new_expression, this);
}
void run(const MatchFinder::MatchResult& result) override {
auto* type = result.Nodes.getNodeAs<clang::CXXRecordDecl>("type");
bool is_optional = (type->getName() == "optional");
auto* arg_type = result.Nodes.getNodeAs<clang::CXXRecordDecl>("gctype");
bool is_gced = arg_type;
if (!arg_type) {
arg_type = result.Nodes.getNodeAs<clang::CXXRecordDecl>("traceable");
}
assert(arg_type);
if (auto* bad_decl = result.Nodes.getNodeAs<clang::Decl>("bad_decl")) {
if (Config::IsIgnoreAnnotated(bad_decl)) {
return;
}
if (is_optional && !is_gced && IsOnStack(bad_decl, record_cache_)) {
return;
}
if (is_optional) {
diagnostics_.OptionalDeclUsedWithGC(bad_decl, type, arg_type);
} else {
diagnostics_.RawPtrOrRefDeclUsedWithGC(bad_decl, type, arg_type);
}
} else {
auto* bad_new = result.Nodes.getNodeAs<clang::Expr>("bad_new");
assert(bad_new);
if (is_optional) {
diagnostics_.OptionalNewExprUsedWithGC(bad_new, type, arg_type);
} else {
diagnostics_.RawPtrOrRefNewExprUsedWithGC(bad_new, type, arg_type);
}
}
}
private:
DiagnosticsReporter& diagnostics_;
RecordCache& record_cache_;
};
class OptionalMemberMatcher : public MatchFinder::MatchCallback {
public:
OptionalMemberMatcher(DiagnosticsReporter& diagnostics,
RecordCache& record_cache)
: diagnostics_(diagnostics), record_cache_(record_cache) {}
void Register(MatchFinder& match_finder) {
auto optional_gced_type =
hasType(classTemplateSpecializationDecl(
hasAnyName("::absl::optional", "::std::optional"),
hasTemplateArgument(0, refersToType(MemberType())))
.bind("type"));
auto optional_field = fieldDecl(optional_gced_type).bind("bad_decl");
auto optional_new_expression =
cxxNewExpr(has(cxxConstructExpr(optional_gced_type))).bind("bad_new");
match_finder.addDynamicMatcher(optional_field, this);
match_finder.addDynamicMatcher(optional_new_expression, this);
}
void run(const MatchFinder::MatchResult& result) override {
auto* type = result.Nodes.getNodeAs<clang::CXXRecordDecl>("type");
auto* member = result.Nodes.getNodeAs<clang::CXXRecordDecl>("member");
if (auto* bad_decl = result.Nodes.getNodeAs<clang::Decl>("bad_decl")) {
if (Config::IsIgnoreAnnotated(bad_decl) ||
IsOnStack(bad_decl, record_cache_)) {
return;
}
diagnostics_.OptionalDeclUsedWithMember(bad_decl, type, member);
} else {
auto* bad_new = result.Nodes.getNodeAs<clang::Expr>("bad_new");
assert(bad_new);
diagnostics_.OptionalNewExprUsedWithMember(bad_new, type, member);
}
}
private:
DiagnosticsReporter& diagnostics_;
RecordCache& record_cache_;
};
class CollectionOfGarbageCollectedMatcher : public MatchFinder::MatchCallback {
public:
explicit CollectionOfGarbageCollectedMatcher(DiagnosticsReporter& diagnostics,
RecordCache& record_cache)
: diagnostics_(diagnostics), record_cache_(record_cache) {}
void Register(MatchFinder& match_finder) {
auto gced_ptr_or_ref =
anyOf(GarbageCollectedType(),
pointerType(pointee(GarbageCollectedType())).bind("ptr"),
referenceType(pointee(GarbageCollectedType())).bind("ptr"));
auto gced_ptr_ref_or_pair =
anyOf(gced_ptr_or_ref,
hasCanonicalType(hasDeclaration((classTemplateSpecializationDecl(
hasName("::std::pair"),
hasAnyTemplateArgument(refersToType(gced_ptr_or_ref)))))));
auto member_ptr_or_ref =
anyOf(MemberType(), pointerType(pointee(MemberType())),
referenceType(pointee(MemberType())));
auto member_ptr_ref_or_pair =
anyOf(member_ptr_or_ref,
hasCanonicalType(hasDeclaration((classTemplateSpecializationDecl(
hasName("::std::pair"),
hasAnyTemplateArgument(refersToType(member_ptr_or_ref)))))));
auto gced_or_member = anyOf(gced_ptr_ref_or_pair, member_ptr_ref_or_pair);
auto has_wtf_collection_name =
hasAnyName("::blink::Vector", "::blink::Deque", "::blink::HashSet",
"::blink::LinkedHashSet", "::blink::HashCountedSet",
"::blink::HashMap");
auto has_std_collection_name =
hasAnyName("::std::vector", "::std::map", "::std::unordered_map",
"::std::set", "::std::unordered_set", "::std::array");
auto partition_allocator = hasCanonicalType(
hasDeclaration(cxxRecordDecl(hasName("::blink::PartitionAllocator"))));
auto wtf_collection_decl =
classTemplateSpecializationDecl(
has_wtf_collection_name,
hasAnyTemplateArgument(refersToType(gced_or_member)),
hasAnyTemplateArgument(refersToType(partition_allocator)))
.bind("collection");
auto std_collection_decl =
classTemplateSpecializationDecl(
has_std_collection_name,
hasAnyTemplateArgument(refersToType(gced_or_member)))
.bind("collection");
auto any_collection = hasType(hasCanonicalType(
hasDeclaration(anyOf(wtf_collection_decl, std_collection_decl))));
auto collection_field = fieldDecl(any_collection).bind("bad_decl");
auto collection_var = varDecl(any_collection).bind("bad_decl");
auto collection_new_expression =
cxxNewExpr(has(cxxConstructExpr(any_collection))).bind("bad_new");
match_finder.addDynamicMatcher(collection_field, this);
match_finder.addDynamicMatcher(collection_var, this);
match_finder.addDynamicMatcher(collection_new_expression, this);
}
void run(const MatchFinder::MatchResult& result) override {
auto* collection =
result.Nodes.getNodeAs<clang::CXXRecordDecl>("collection");
auto* gc_type = result.Nodes.getNodeAs<clang::CXXRecordDecl>("gctype");
auto* member = result.Nodes.getNodeAs<clang::CXXRecordDecl>("member");
assert(gc_type || member);
if (auto* bad_decl = result.Nodes.getNodeAs<clang::Decl>("bad_decl")) {
if (Config::IsIgnoreAnnotated(bad_decl)) {
return;
}
if (collection->getNameAsString() == "array") {
if (member) {
return;
}
if (result.Nodes.getNodeAs<clang::Type>("ptr") &&
IsOnStack(bad_decl, record_cache_)) {
return;
}
}
if (gc_type) {
diagnostics_.CollectionOfGCed(bad_decl, collection, gc_type);
} else {
assert(member);
diagnostics_.CollectionOfMembers(bad_decl, collection, member);
}
} else {
auto* bad_new = result.Nodes.getNodeAs<clang::Expr>("bad_new");
assert(bad_new);
if (gc_type) {
diagnostics_.CollectionOfGCed(bad_new, collection, gc_type);
} else {
assert(member);
diagnostics_.CollectionOfMembers(bad_new, collection, member);
}
}
}
private:
DiagnosticsReporter& diagnostics_;
RecordCache& record_cache_;
};
AST_MATCHER_P(clang::TemplateArgument,
parameterPackHasAnyElement,
clang::ast_matchers::internal::Matcher<clang::TemplateArgument>,
InnerMatcher) {
if (Node.getKind() != clang::TemplateArgument::Pack) {
return false;
}
return llvm::any_of(Node.pack_elements(),
[&](const clang::TemplateArgument& Arg) {
return InnerMatcher.matches(Arg, Finder, Builder);
});
}
class VariantGarbageCollectedMatcher : public MatchFinder::MatchCallback {
public:
explicit VariantGarbageCollectedMatcher(DiagnosticsReporter& diagnostics)
: diagnostics_(diagnostics) {}
void Register(MatchFinder& match_finder) {
auto variant_construction =
cxxConstructExpr(
hasDeclaration(cxxConstructorDecl(
ofClass(classTemplateSpecializationDecl(
hasAnyName("::absl::variant", "::std::variant"),
hasAnyTemplateArgument(parameterPackHasAnyElement(
refersToType(GarbageCollectedType()))))
.bind("variant")))))
.bind("bad");
match_finder.addDynamicMatcher(variant_construction, this);
}
void run(const MatchFinder::MatchResult& result) override {
auto* bad_use = result.Nodes.getNodeAs<clang::Expr>("bad");
auto* variant = result.Nodes.getNodeAs<clang::CXXRecordDecl>("variant");
auto* gc_type = result.Nodes.getNodeAs<clang::CXXRecordDecl>("gctype");
diagnostics_.VariantUsedWithGC(bad_use, variant, gc_type);
}
private:
DiagnosticsReporter& diagnostics_;
};
class MemberOnStackMatcher : public MatchFinder::MatchCallback {
public:
explicit MemberOnStackMatcher(DiagnosticsReporter& diagnostics)
: diagnostics_(diagnostics) {}
void Register(MatchFinder& match_finder) {
auto class_member_variable_matcher =
varDecl(hasType(MemberType())).bind("var");
match_finder.addDynamicMatcher(class_member_variable_matcher, this);
}
void run(const MatchFinder::MatchResult& result) override {
auto* member = result.Nodes.getNodeAs<clang::VarDecl>("var");
if (Config::IsIgnoreAnnotated(member)) {
return;
}
diagnostics_.MemberOnStack(member);
}
private:
DiagnosticsReporter& diagnostics_;
};
class WeakPtrToGCedMatcher : public MatchFinder::MatchCallback {
public:
explicit WeakPtrToGCedMatcher(DiagnosticsReporter& diagnostics)
: diagnostics_(diagnostics) {}
void Register(MatchFinder& match_finder) {
auto weak_ptr_type = hasType(
classTemplateSpecializationDecl(
hasAnyName("::base::WeakPtr", "::base::WeakPtrFactory"),
hasTemplateArgument(0, refersToType(GarbageCollectedType())))
.bind("weak_ptr"));
auto weak_ptr_field = fieldDecl(weak_ptr_type).bind("bad_decl");
auto weak_ptr_var = varDecl(weak_ptr_type).bind("bad_decl");
auto weak_ptr_new_expression =
cxxNewExpr(has(cxxConstructExpr(weak_ptr_type))).bind("bad_decl");
match_finder.addDynamicMatcher(weak_ptr_field, this);
match_finder.addDynamicMatcher(weak_ptr_var, this);
match_finder.addDynamicMatcher(weak_ptr_new_expression, this);
}
void run(const MatchFinder::MatchResult& result) override {
auto* decl = result.Nodes.getNodeAs<clang::Decl>("bad_decl");
if (Config::IsIgnoreAnnotated(decl)) {
return;
}
auto* weak_ptr = result.Nodes.getNodeAs<clang::CXXRecordDecl>("weak_ptr");
auto* gc_type = result.Nodes.getNodeAs<clang::CXXRecordDecl>("gctype");
diagnostics_.WeakPtrToGCed(decl, weak_ptr, gc_type);
}
private:
DiagnosticsReporter& diagnostics_;
};
AST_MATCHER(clang::CXXRecordDecl, isDisallowedNewClass) {
auto& context = Finder->getASTContext();
auto gc_matcher = GarbageCollectedType();
if (gc_matcher.matches(context.getCanonicalTagType(&Node), Finder, Builder)) {
return false;
}
auto method = std::find_if(
Node.method_begin(), Node.method_end(), [](const auto& method) {
return method->getNameAsString() == kNewOperatorName &&
method->getNumParams() == 1;
});
if (method != Node.method_end()) {
return method->isDeleted();
}
for (auto& base_spec : Node.bases()) {
if (auto* base = base_spec.getType()->getAsCXXRecordDecl()) {
if (matches(*base, Finder, Builder)) {
return true;
}
}
}
return false;
}
size_t RoundUp(size_t value, size_t align) {
assert((align & (align - 1)) == 0);
return (value + align - 1) & ~(align - 1);
}
size_t RequiredSizeForFields(const clang::ASTContext& context,
size_t current_size,
const std::vector<clang::QualType>& field_types) {
size_t largest_field_alignment = 0;
for (clang::QualType type : field_types) {
assert(!type->isDependentType());
const size_t current_field_alignment = context.getTypeAlign(type);
current_size = RoundUp(current_size, current_field_alignment);
current_size += context.getTypeSize(type);
largest_field_alignment =
std::max(largest_field_alignment, current_field_alignment);
}
current_size = RoundUp(current_size, largest_field_alignment);
return current_size;
}
class PaddingInGCedMatcher : public MatchFinder::MatchCallback {
public:
PaddingInGCedMatcher(clang::ASTContext& context,
DiagnosticsReporter& diagnostics)
: context_(context), diagnostics_(diagnostics) {}
void Register(MatchFinder& match_finder) {
auto member_field_matcher =
cxxRecordDecl(has(fieldDecl(hasType(MemberType())).bind("field")),
isDisallowedNewClass())
.bind("record");
match_finder.addMatcher(member_field_matcher, this);
}
void run(const MatchFinder::MatchResult& result) override {
auto* class_decl = result.Nodes.getNodeAs<clang::RecordDecl>("record");
if (class_decl->isDependentType() || class_decl->isUnion()) {
return;
}
if (auto* member_decl = result.Nodes.getNodeAs<clang::FieldDecl>("field");
member_decl && Config::IsIgnoreAnnotated(member_decl)) {
return;
}
if (auto* cxx_record_decl =
clang::dyn_cast<clang::CXXRecordDecl>(class_decl)) {
if (cxx_record_decl->getNumVBases()) {
return;
}
}
std::vector<clang::QualType> fields;
for (auto* field : class_decl->fields()) {
if (field->isBitField()) {
return;
}
if (field->isZeroSize(context_)) {
return;
}
if (field->hasAttr<clang::AlignedAttr>()) {
return;
}
fields.push_back(field->getType());
}
assert(fields.size() > 0);
const clang::ASTRecordLayout& layout =
context_.getASTRecordLayout(class_decl);
const size_t base_size = layout.getFieldOffset(0);
const size_t size_before =
RequiredSizeForFields(context_, base_size, fields);
std::sort(fields.begin(), fields.end(),
[this](clang::QualType t1, clang::QualType t2) {
return context_.getTypeSize(t1) > context_.getTypeSize(t2);
});
const size_t size_after =
RequiredSizeForFields(context_, base_size, fields);
if (size_after < size_before) {
diagnostics_.AdditionalPadding(
class_decl, (size_before - size_after) / context_.getCharWidth());
}
}
private:
clang::ASTContext& context_;
DiagnosticsReporter& diagnostics_;
};
class GCedVarOrField : public MatchFinder::MatchCallback {
public:
explicit GCedVarOrField(DiagnosticsReporter& diagnostics)
: diagnostics_(diagnostics) {}
void Register(MatchFinder& match_finder) {
auto gced_field =
fieldDecl(hasType(GarbageCollectedType())).bind("bad_field");
auto unimplemented_template_instance_parameter =
parmVarDecl(hasAncestor(functionDecl(hasParent(functionTemplateDecl()),
unless(hasBody(stmt())))));
auto gced_var = varDecl(hasType(GarbageCollectedType()),
unless(unimplemented_template_instance_parameter))
.bind("bad_var");
match_finder.addDynamicMatcher(gced_field, this);
match_finder.addDynamicMatcher(gced_var, this);
}
void run(const MatchFinder::MatchResult& result) override {
const auto* gctype = result.Nodes.getNodeAs<clang::CXXRecordDecl>("gctype");
assert(gctype);
const auto* field = result.Nodes.getNodeAs<clang::FieldDecl>("bad_field");
if (field) {
if (Config::IsIgnoreAnnotated(field)) {
return;
}
diagnostics_.GCedField(field, gctype);
} else {
const auto* var = result.Nodes.getNodeAs<clang::VarDecl>("bad_var");
assert(var);
if (Config::IsIgnoreAnnotated(var)) {
return;
}
diagnostics_.GCedVar(var, gctype);
}
}
private:
DiagnosticsReporter& diagnostics_;
};
}
void FindBadPatterns(clang::ASTContext& ast_context,
DiagnosticsReporter& diagnostics,
RecordCache& record_cache,
const BlinkGCPluginOptions& options) {
MatchFinder match_finder;
UniquePtrGarbageCollectedMatcher unique_ptr_gc(diagnostics);
unique_ptr_gc.Register(match_finder);
OptionalOrRawPtrToGCedMatcher optional_or_rawptr_gc(diagnostics,
record_cache);
optional_or_rawptr_gc.Register(match_finder);
CollectionOfGarbageCollectedMatcher collection_of_gc(diagnostics,
record_cache);
if (options.enable_off_heap_collections_of_gced_check) {
collection_of_gc.Register(match_finder);
}
VariantGarbageCollectedMatcher variant_gc(diagnostics);
variant_gc.Register(match_finder);
MemberOnStackMatcher member_on_stack(diagnostics);
if (options.enable_members_on_stack_check) {
member_on_stack.Register(match_finder);
}
PaddingInGCedMatcher padding_in_gced(ast_context, diagnostics);
if (options.enable_extra_padding_check) {
padding_in_gced.Register(match_finder);
}
WeakPtrToGCedMatcher weak_ptr_to_gced(diagnostics);
weak_ptr_to_gced.Register(match_finder);
GCedVarOrField gced_var_or_field(diagnostics);
gced_var_or_field.Register(match_finder);
OptionalMemberMatcher optional_member(diagnostics, record_cache);
optional_member.Register(match_finder);
match_finder.matchAST(ast_context);
}