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