Stefan Zager | e9734a5c | 2023-01-20 22:44:46 | [diff] [blame] | 1 | // 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 | |
| 12 | namespace chrome_checker { |
| 13 | |
| 14 | namespace { |
| 15 | |
| 16 | const 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 | |
| 21 | const 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 | |
mikt | c61fb1fb | 2023-04-26 05:13:51 | [diff] [blame] | 36 | bool StackAllocatedPredicate::IsStackAllocated( |
| 37 | const clang::CXXRecordDecl* record) const { |
Stefan Zager | e9734a5c | 2023-01-20 22:44:46 | [diff] [blame] | 38 | 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()) { |
mikt | c61fb1fb | 2023-04-26 05:13:51 | [diff] [blame] | 62 | for (clang::CXXRecordDecl::base_class_const_iterator it = |
| 63 | record->bases_begin(); |
Stefan Zager | e9734a5c | 2023-01-20 22:44:46 | [diff] [blame] | 64 | !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 | |
mikt | c61fb1fb | 2023-04-26 05:13:51 | [diff] [blame] | 100 | StackAllocatedChecker::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 Zager | e9734a5c | 2023-01-20 22:44:46 | [diff] [blame] | 107 | void StackAllocatedChecker::Check(clang::CXXRecordDecl* record) { |
Stefan Zager | 615a899 | 2023-02-16 21:05:44 | [diff] [blame] | 108 | if (!record->isCompleteDefinition()) { |
| 109 | return; |
| 110 | } |
Stefan Zager | e9734a5c | 2023-01-20 22:44:46 | [diff] [blame] | 111 | // If this type is stack allocated, no need to check fields. |
mikt | c61fb1fb | 2023-04-26 05:13:51 | [diff] [blame] | 112 | if (predicate_.IsStackAllocated(record)) { |
Stefan Zager | e9734a5c | 2023-01-20 22:44:46 | [diff] [blame] | 113 | 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 | |
mikt | c61fb1fb | 2023-04-26 05:13:51 | [diff] [blame] | 139 | if (predicate_.IsStackAllocated(field_record)) { |
Stefan Zager | e9734a5c | 2023-01-20 22:44:46 | [diff] [blame] | 140 | compiler_.getDiagnostics().Report(field->getLocation(), |
| 141 | stack_allocated_field_error_signature_) |
| 142 | << record->getName() << field->getNameAsString(); |
| 143 | } |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | } // namespace chrome_checker |