blob: 2f033a9b6875cd9fa758c6e660253f91fed625ec [file] [log] [blame]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "lifetime_analysis/visit_lifetimes.h"
#include <string>
#include <utility>
#include "lifetime_analysis/object.h"
#include "lifetime_analysis/object_set.h"
#include "lifetime_annotations/pointee_type.h"
#include "lifetime_annotations/type_lifetimes.h"
#include "clang/AST/Attr.h"
#include "clang/AST/Attrs.inc"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/Type.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
namespace clang {
namespace tidy {
namespace lifetimes {
namespace {
llvm::SmallVector<std::string> GetFieldLifetimeArguments(
const clang::FieldDecl* field) {
// TODO(mboehme): Report errors as Clang diagnostics, not through
// llvm::report_fatal_error().
const clang::AnnotateAttr* member_lifetimes_attr = nullptr;
for (auto annotate : field->specific_attrs<clang::AnnotateAttr>()) {
if (annotate->getAnnotation() == "member_lifetimes") {
if (member_lifetimes_attr) {
llvm::report_fatal_error("repeated lifetime annotation");
}
member_lifetimes_attr = annotate;
}
}
if (!member_lifetimes_attr) {
return {};
}
llvm::SmallVector<std::string> ret;
for (const auto& arg : member_lifetimes_attr->args()) {
llvm::StringRef lifetime;
if (llvm::Error err = EvaluateAsStringLiteral(arg, field->getASTContext())
.moveInto(lifetime)) {
llvm::report_fatal_error(llvm::StringRef(toString(std::move(err))));
}
ret.push_back(lifetime.str());
}
return ret;
}
template <typename Callback>
void ForEachField(ObjectSet objects, clang::QualType record_type,
const ObjectLifetimes& object_lifetimes,
LifetimeVisitor& visitor, const Callback& callback) {
for (clang::FieldDecl* f :
record_type->getAs<clang::RecordType>()->getDecl()->fields()) {
ObjectLifetimes field_lifetimes = object_lifetimes.GetFieldOrBaseLifetimes(
f->getType(), GetFieldLifetimeArguments(f));
callback(objects, field_lifetimes, f);
}
if (auto* cxxrecord = clang::dyn_cast<clang::CXXRecordDecl>(
record_type->getAs<clang::RecordType>()->getDecl())) {
for (const clang::CXXBaseSpecifier& base : cxxrecord->bases()) {
auto base_object_lifetimes = object_lifetimes.GetFieldOrBaseLifetimes(
base.getType(), GetLifetimeParameters(base.getType()));
auto base_object = visitor.GetBaseClassObject(objects, base.getType());
ObjectSet next_objects = objects;
next_objects.Add(base_object);
ForEachField(next_objects, base.getType(), base_object_lifetimes, visitor,
callback);
}
}
}
void VisitLifetimesImpl(const ObjectSet& points_to_set,
const ObjectLifetimes& object_lifetimes,
llvm::DenseSet<const Object*>& visited_objects,
LifetimeVisitor& visitor, int pointee_depth);
// Traverse fields while walking up base classes. This can be a bit wasteful
// for cases like diamond inheritance (which is hopefully not common).
void TraverseObjectFieldsWithBases(
const ObjectSet& object_set, clang::QualType record_type,
const ObjectLifetimes& object_lifetimes,
llvm::DenseSet<const Object*>& visited_object, LifetimeVisitor& visitor,
int pointee_depth) {
assert(record_type->isRecordType());
if (record_type->isIncompleteType()) {
return;
}
// Our analysis relies on objects reachable in the same way to be visited in
// the same call, thus we need to "merge" together the `Object`s that come
// from the same field but different `object`s in the object_set.
llvm::SmallVector<std::pair<ObjectSet, ObjectLifetimes>> fields_to_visit;
for (const Object* object : object_set) {
// This code relies on the vist order of ForEachField being independent
// of `object`.
size_t next_field = 0;
ForEachField(
{object}, record_type, object_lifetimes, visitor,
[&](const ObjectSet& bases, const ObjectLifetimes& field_lifetimes,
const clang::FieldDecl* f) {
size_t field = next_field++;
if (field == fields_to_visit.size()) {
fields_to_visit.emplace_back(ObjectSet(),
std::move(field_lifetimes));
}
const Object* field_object = visitor.GetFieldObject(bases, f);
fields_to_visit[field].first.Add(field_object);
});
}
for (auto [objects, lifetimes] : std::move(fields_to_visit)) {
VisitLifetimesImpl(objects, lifetimes, visited_object, visitor,
pointee_depth);
}
}
void VisitLifetimesImpl(const ObjectSet& points_to_set,
const ObjectLifetimes& object_lifetimes,
llvm::DenseSet<const Object*>& visited_objects,
LifetimeVisitor& visitor, int pointee_depth) {
size_t num_visited_before = visited_objects.size();
for (const Object* object : points_to_set) {
visited_objects.insert(object);
}
if (num_visited_before == visited_objects.size()) {
// No new object -> nothing to do. This avoids infinite loops.
return;
}
if (const clang::QualType type = object_lifetimes.GetValueLifetimes().Type();
type->isRecordType()) {
TraverseObjectFieldsWithBases(points_to_set, type, object_lifetimes,
visited_objects, visitor, pointee_depth);
}
// TODO(veluca): here we call Traverse even when there is no child type.
// This is likely an indication that it is better to split up Traverse into
// multiple methods.
clang::QualType child_type =
PointeeType(object_lifetimes.GetValueLifetimes().Type());
ObjectSet child_object =
visitor.Traverse(object_lifetimes, points_to_set, pointee_depth);
if (!child_object.empty() && !child_type.isNull()) {
VisitLifetimesImpl(
child_object,
object_lifetimes.GetValueLifetimes().GetPointeeLifetimes(),
visited_objects, visitor, pointee_depth + 1);
}
}
} // namespace
void VisitLifetimes(const ObjectSet& points_to_set, clang::QualType type,
const ObjectLifetimes& object_lifetimes,
LifetimeVisitor& visitor) {
llvm::DenseSet<const Object*> visited_objects;
VisitLifetimesImpl(points_to_set, object_lifetimes, visited_objects, visitor,
/*pointee_depth=*/0);
}
} // namespace lifetimes
} // namespace tidy
} // namespace clang