1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
|
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "StackAllocatedChecker.h"
#include "clang/AST/Attr.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/Frontend/CompilerInstance.h"
namespace chrome_checker {
namespace {
const char kStackAllocatedFieldError[] =
"Non-stack-allocated type '%0' has a field '%1' which is a stack-allocated "
"type, pointer/reference to a stack-allocated type, or template "
"instantiation with a stack-allocated type as template parameter.";
const clang::Type* StripReferences(const clang::Type* type) {
while (type) {
if (type->isArrayType()) {
type = type->getPointeeOrArrayElementType();
} else if (type->isPointerType() || type->isReferenceType()) {
type = type->getPointeeType().getTypePtrOrNull();
} else {
break;
}
}
return type;
}
} // namespace
bool StackAllocatedPredicate::IsStackAllocated(
const clang::CXXRecordDecl* record) const {
if (!record) {
return false;
}
auto iter = cache_.find(record);
if (iter != cache_.end()) {
return iter->second;
}
bool stack_allocated = false;
// Check member fields
for (clang::Decl* decl : record->decls()) {
clang::TypeAliasDecl* alias = clang::dyn_cast<clang::TypeAliasDecl>(decl);
if (!alias) {
continue;
}
if (alias->getName() == "IsStackAllocatedTypeMarker") {
stack_allocated = true;
break;
}
}
// Check base classes
if (record->hasDefinition()) {
for (clang::CXXRecordDecl::base_class_const_iterator it =
record->bases_begin();
!stack_allocated && it != record->bases_end(); ++it) {
clang::CXXRecordDecl* parent_record =
it->getType().getTypePtr()->getAsCXXRecordDecl();
stack_allocated = IsStackAllocated(parent_record);
}
}
// If we don't create a cache record now, it's possible to get into infinite
// mutual recursion between the base class check (above) and the template
// parameter check (below).
iter = cache_.insert({record, stack_allocated}).first;
// Check template parameters. This is aggressive and can cause false positives
// -- a templated class doesn't necessarily store instances of its type
// parameters, in which case it need not be stack-allocated. In practice,
// though, this kind of false positive is rare; and conservatively marking
// this type as stack-allocated will catch cases where a type parameter
// doesn't have a full type definition in the translation unit.
if (auto* field_record_template =
clang::dyn_cast<clang::ClassTemplateSpecializationDecl>(record)) {
const auto& template_args = field_record_template->getTemplateArgs();
for (unsigned i = 0; i < template_args.size(); i++) {
if (template_args[i].getKind() == clang::TemplateArgument::Type) {
const auto* type =
StripReferences(template_args[i].getAsType().getTypePtrOrNull());
if (type && IsStackAllocated(type->getAsCXXRecordDecl())) {
stack_allocated = true;
}
}
}
}
iter->second = stack_allocated;
return stack_allocated;
}
StackAllocatedChecker::StackAllocatedChecker(clang::CompilerInstance& compiler)
: compiler_(compiler),
stack_allocated_field_error_signature_(
compiler.getDiagnostics().getCustomDiagID(
clang::DiagnosticsEngine::Error,
kStackAllocatedFieldError)) {}
void StackAllocatedChecker::Check(clang::CXXRecordDecl* record) {
if (!record->isCompleteDefinition()) {
return;
}
// If this type is stack allocated, no need to check fields.
if (predicate_.IsStackAllocated(record)) {
return;
}
for (clang::RecordDecl::field_iterator it = record->field_begin();
it != record->field_end(); ++it) {
clang::FieldDecl* field = *it;
bool ignore = false;
for (auto annotation : field->specific_attrs<clang::AnnotateAttr>()) {
if (annotation->getAnnotation() == "stack_allocated_ignore") {
ignore = true;
break;
}
}
if (ignore) {
continue;
}
const clang::Type* type =
StripReferences(field->getType().getTypePtrOrNull());
if (!type) {
continue;
}
auto* field_record = type->getAsCXXRecordDecl();
if (!field_record) {
continue;
}
if (predicate_.IsStackAllocated(field_record)) {
compiler_.getDiagnostics().Report(field->getLocation(),
stack_allocated_field_error_signature_)
<< record->getName() << field->getNameAsString();
}
}
}
} // namespace chrome_checker
|