blob: bc7b5fe0060e055be3d0a5b4c0d207372db69af8 [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/object_repository.h"
#include <cassert>
#include <cstddef>
#include <optional>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "absl/strings/str_cat.h"
#include "lifetime_analysis/builtin_lifetimes.h"
#include "lifetime_analysis/object.h"
#include "lifetime_analysis/object_set.h"
#include "lifetime_analysis/points_to_map.h"
#include "lifetime_annotations/function_lifetimes.h"
#include "lifetime_annotations/lifetime.h"
#include "lifetime_annotations/pointee_type.h"
#include "lifetime_annotations/type_lifetimes.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/ExprObjC.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/Stmt.h"
#include "clang/AST/Type.h"
#include "clang/Basic/LLVM.h"
#include "clang/Basic/Specifiers.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/raw_ostream.h"
namespace clang {
namespace tidy {
namespace lifetimes {
FunctionLifetimesOrError GetFunctionLifetimes(
const FunctionDecl* decl, const FunctionLifetimesMap& known_lifetimes) {
// Note that this cannot be a simple .find() on the map because `decl` might
// be a builtin.
bool is_builtin = decl->getBuiltinID() != 0;
if (is_builtin) {
return GetBuiltinLifetimes(decl);
}
if (!known_lifetimes.count(decl->getCanonicalDecl())) {
return FunctionAnalysisError(
absl::StrCat("Unknown callee ", decl->getNameAsString()));
}
return known_lifetimes.lookup(decl->getCanonicalDecl());
}
class ObjectRepository::VarDeclVisitor
: public clang::RecursiveASTVisitor<VarDeclVisitor> {
public:
explicit VarDeclVisitor(ObjectRepository& object_repository,
const FunctionLifetimesMap& callee_lifetimes)
: object_repository_(object_repository),
callee_lifetimes_(callee_lifetimes) {}
// We need to visit implicitly-defined constructors and assignment operators.
bool shouldVisitImplicitCode() { return true; }
bool VisitVarDecl(clang::VarDecl* var) {
// Add objects for any local variables declared in this function.
AddObjectForVar(var);
return true;
}
bool VisitReturnStmt(clang::ReturnStmt* stmt) {
const clang::Expr* expr = stmt->getRetValue();
if (IsInitExprInitializingARecordObject(expr)) {
PropagateResultObject(expr, object_repository_.return_object_);
}
return true;
}
bool VisitMemberExpr(clang::MemberExpr* member) {
if (auto* method =
clang::dyn_cast<clang::CXXMethodDecl>(member->getMemberDecl());
method && method->isStatic()) {
// Create objects for static member functions.
AddObjectForFunc(method);
}
return true;
}
bool VisitDeclRefExpr(clang::DeclRefExpr* decl_ref) {
// Add objects for any global variables referenced in this function.
// This also runs for local variables, but we don't have to treat those
// differently as AddObjectForVar() protects against duplication.
if (auto* var_decl = clang::dyn_cast<clang::VarDecl>(decl_ref->getDecl())) {
AddObjectForVar(var_decl);
}
// Add objects for any function referenced in this function.
if (auto* function_decl =
clang::dyn_cast<clang::FunctionDecl>(decl_ref->getDecl())) {
AddObjectForFunc(function_decl);
}
return true;
}
bool VisitObjCMessageExpr(clang::ObjCMessageExpr* msg_expr) {
// ObjCMessageExpr is an initializer expression terminator, so we should
// have walked down from the result object to find its terminating
// expressions, which should have found this expression and connected it to
// that object already.
if (!object_repository_.result_objects_.count(msg_expr)) {
msg_expr->dump();
llvm::report_fatal_error(
"Missing initializer for ObjCMessageExpr, we did not record it "
"when we visited something earlier in the tree yet?");
}
return true;
}
// Create objects for function call arguments.
bool VisitCallExpr(clang::CallExpr* call_expr) {
if (IsInitExprInitializingARecordObject(call_expr)) {
assert(ResultObjectWasPropagatedTo(call_expr));
}
FunctionLifetimeFactorySingleCallback lifetime_factory(
[](auto) { return Lifetime::CreateVariable(); });
// If we have a direct callee, construct a FunctionLifetimes out of the
// function/method definition.
if (auto callee = call_expr->getDirectCallee()) {
bool is_operator_call = clang::isa<clang::CXXOperatorCallExpr>(call_expr);
bool is_method = clang::isa<clang::CXXMethodDecl>(callee);
object_repository_.call_expr_virtual_lifetimes_[call_expr] =
FunctionLifetimes::CreateForDecl(callee, lifetime_factory).get();
PrepareFunctionCall(
call_expr, /*index_shift=*/is_operator_call && is_method ? 1 : 0);
} else {
// Always a function pointer.
// TODO(veluca): pointers-to-members are not supported (yet?)
clang::QualType callee_type =
call_expr->getCallee()->getType()->getPointeeType().IgnoreParens();
// TODO(veluca): what about FunctionNoProtoType??
object_repository_.call_expr_virtual_lifetimes_[call_expr] =
FunctionLifetimes::CreateForFunctionType(
clang::cast<clang::FunctionProtoType>(callee_type),
lifetime_factory)
.get();
PrepareFunctionCall(call_expr, /*index_shift=*/0);
}
return true;
}
bool VisitStringLiteral(clang::StringLiteral* lit_expr) {
object_repository_.string_literal_object_ = object_repository_.CreateObject(
lit_expr->getType(), Lifetime::Static(),
[](const clang::Expr*) { return Lifetime::Static(); });
return true;
}
bool VisitCXXConstructExpr(clang::CXXConstructExpr* construct_expr) {
assert(ResultObjectWasPropagatedTo(construct_expr));
FunctionLifetimeFactorySingleCallback lifetime_factory(
[](auto) { return Lifetime::CreateVariable(); });
const clang::FunctionDecl* constructor = construct_expr->getConstructor();
object_repository_.call_expr_virtual_lifetimes_[construct_expr] =
FunctionLifetimes::CreateForDecl(constructor, lifetime_factory).get();
PrepareFunctionCall(construct_expr,
/*index_shift=*/0);
return true;
}
bool VisitInitListExpr(clang::InitListExpr* init_list_expr) {
// We only want to visit in Semantic form, we ignore Syntactic form.
if (IsInitExprInitializingARecordObject(init_list_expr) &&
init_list_expr->isSemanticForm() && !init_list_expr->isTransparent()) {
assert(ResultObjectWasPropagatedTo(init_list_expr));
}
return true;
}
bool VisitMaterializeTemporaryExpr(
clang::MaterializeTemporaryExpr* temporary_expr) {
object_repository_.temporary_objects_[temporary_expr] =
AddTemporaryObjectForExpression(temporary_expr->getSubExpr());
return true;
}
bool VisitCompoundStmt(clang::CompoundStmt* compound) {
// Create temporary objects for any top-level `CXXTemporaryObjectExpr`s,
// i.e. ones that are used as statements.
for (clang::Stmt* stmt : compound->body()) {
if (auto* temporary = clang::dyn_cast<CXXTemporaryObjectExpr>(stmt)) {
AddTemporaryObjectForExpression(temporary);
}
}
return true;
}
void PrepareFunctionCall(const clang::Expr* expr, size_t index_shift) {
const auto& func_lifetimes =
object_repository_.call_expr_virtual_lifetimes_[expr];
auto make_object = [this](const ValueLifetimes& lifetime) {
return object_repository_.CreateObject(
ObjectLifetimes(Lifetime::CreateLocal(), lifetime),
object_repository_.initial_points_to_map_);
};
for (size_t i = 0; i < func_lifetimes.GetNumParams(); ++i) {
object_repository_
.call_expr_args_objects_[std::make_pair(expr, i + index_shift)] =
make_object(func_lifetimes.GetParamLifetimes(i));
}
if (func_lifetimes.IsNonStaticMethod()) {
object_repository_.call_expr_this_pointers_[expr] =
make_object(func_lifetimes.GetThisLifetimes());
}
object_repository_.call_expr_ret_objects_[expr] =
make_object(func_lifetimes.GetReturnLifetimes());
}
void AddObjectForVar(clang::VarDecl* var) {
if (object_repository_.object_repository_.count(var)) {
return;
}
Lifetime lifetime;
LifetimeFactory lifetime_factory;
switch (var->getStorageClass()) {
case clang::SC_Extern:
case clang::SC_Static:
case clang::SC_PrivateExtern:
lifetime = Lifetime::Static();
lifetime_factory = [](const clang::Expr*) {
return Lifetime::Static();
};
break;
default:
lifetime = Lifetime::CreateLocal();
lifetime_factory = [](const clang::Expr*) {
return Lifetime::CreateVariable();
};
break;
}
const Object* object = object_repository_.CreateObject(
var->getType(), lifetime, lifetime_factory);
object_repository_.object_repository_[var] = object;
if (!var->getType()->isArrayType()) {
object_repository_.initial_single_valued_objects_.Add(object);
}
// Remember the original value of function parameters.
if (auto parm_var_decl = clang::dyn_cast<const clang::ParmVarDecl>(var)) {
object_repository_.initial_parameter_object_[parm_var_decl] =
object_repository_.CloneObject(object);
}
if (var->hasInit() && var->getType()->isRecordType()) {
PropagateResultObject(var->getInit(), object);
}
}
void AddObjectForFunc(clang::FunctionDecl* func) {
if (object_repository_.object_repository_.count(func)) {
return;
}
FunctionLifetimesOrError func_lifetimes =
GetFunctionLifetimes(func, callee_lifetimes_);
if (std::holds_alternative<FunctionAnalysisError>(func_lifetimes)) {
error_ = "No lifetimes for callee '" + func->getNameAsString() +
"': " + std::get<FunctionAnalysisError>(func_lifetimes).message;
} else {
object_repository_.object_repository_[func] =
object_repository_.ConstructObject(
Lifetime::Static(), func->getType(),
std::get<FunctionLifetimes>(func_lifetimes));
}
}
const Object* AddTemporaryObjectForExpression(clang::Expr* expr) {
clang::QualType type = expr->getType().getCanonicalType();
const Object* object = object_repository_.CreateObject(
type, Lifetime::CreateLocal(),
[](const clang::Expr*) { return Lifetime::CreateVariable(); });
if (type->isRecordType()) {
PropagateResultObject(expr, object);
}
return object;
}
// Propagates a result object `object` of record type to the expressions that
// actually perform the initialization (we call these "terminating
// expressions").
//
// `expr` is the initializer for a variable; this will contain one or
// several terminating expressions (such as a CXXConstructExpr, InitListExpr,
// or CallExpr).
//
// Note that not all terminating expressions below `expr` necessarily
// initialize `object`; some of these terminating expressions may also
// initialize temporary objects. This function takes care to propagate
// `object` only to the appropriate terminating expressions.
//
// The mapping from a terminating expression to the result object it
// initializes is stored in `object_repository_.result_objects_`.
void PropagateResultObject(const clang::Expr* expr, const Object* object) {
// TODO(danakj): Use StmtVisitor to implement this method.
// Terminating expressions. Expressions that don't initialize a record
// object can not be such, and their existence is unexpected as we should
// be converting to and initializing a record object from such expressions
// further up in the initializer expression's AST. We will assert later in
// this function if we find this situation somehow due to incorrect
// expectations in this comment.
if (IsInitExprInitializingARecordObject(expr)) {
if (clang::isa<clang::CXXConstructExpr>(expr) ||
clang::isa<clang::CallExpr>(expr) ||
clang::isa<clang::ObjCMessageExpr>(expr) ||
clang::isa<clang::LambdaExpr>(expr)) {
object_repository_.result_objects_[expr] = object;
return;
}
if (auto* e = clang::dyn_cast<clang::InitListExpr>(expr)) {
if (!e->isSemanticForm()) return;
if (e->isTransparent()) {
// A field initializer like `S s{cond ? S{} : S{}}` is considered
// transparent, and the actual initializer is within.
for (const clang::Expr* init : e->inits()) {
PropagateResultObject(init, object);
}
} else {
object_repository_.result_objects_[e] = object;
}
return;
}
}
// Expressions to walk through. Logic is similar to the AggExprEmitter in
// clang third_party/llvm-project/clang/lib/CodeGen/CGExprAgg.cpp though we
// don't have to visit all the sub-expressions that clang codegen needs to,
// as we can stop at terminating expressions and ignore many expressions
// that don't occur in the code we're analyzing.
if (auto* e = clang::dyn_cast<clang::ParenExpr>(expr)) {
PropagateResultObject(e->getSubExpr(), object);
return;
}
if (auto* e = clang::dyn_cast<clang::UnaryOperator>(expr)) {
PropagateResultObject(e->getSubExpr(), object);
return;
}
if (auto* e = clang::dyn_cast<clang::SubstNonTypeTemplateParmExpr>(expr)) {
PropagateResultObject(e->getReplacement(), object);
return;
}
if (auto* e = clang::dyn_cast<clang::CastExpr>(expr)) {
PropagateResultObject(e->getSubExpr(), object);
return;
}
if (auto* e = clang::dyn_cast<clang::CXXDefaultArgExpr>(expr)) {
PropagateResultObject(e->getExpr(), object);
return;
}
if (auto* e = clang::dyn_cast<clang::CXXDefaultInitExpr>(expr)) {
PropagateResultObject(e->getExpr(), object);
return;
}
if (auto* e = clang::dyn_cast<clang::ExprWithCleanups>(expr)) {
PropagateResultObject(e->getSubExpr(), object);
return;
}
// Expressions that produce a temporary object.
if (auto* e = clang::dyn_cast<clang::BinaryOperator>(expr)) {
if (e->isCommaOp()) {
AddTemporaryObjectForExpression(e->getLHS());
PropagateResultObject(e->getRHS(), object);
return;
}
// Any other binary operator should not produce a record type, it would be
// used to construct a record further up the AST, so we should not arrive
// here.
expr->dump();
llvm::report_fatal_error(
"Unexpected binary operator in initializer expression tree");
}
if (auto* e = clang::dyn_cast<clang::AbstractConditionalOperator>(expr)) {
AddTemporaryObjectForExpression(e->getCond());
PropagateResultObject(e->getTrueExpr(), object);
PropagateResultObject(e->getFalseExpr(), object);
return;
}
expr->dump();
llvm::report_fatal_error(
"Unexpected expression in initializer expression tree");
}
bool ResultObjectWasPropagatedTo(clang::Expr* terminating_expr) {
// An expression that initializes an object should have already been
// connected to the result object it initializes. We should have walked down
// from the result object to find its terminating expressions.
if (!object_repository_.result_objects_.count(terminating_expr)) {
llvm::errs() << "Missing result object for terminating expression, "
"we did not record it when we visited something earlier "
"in the tree yet?\n";
terminating_expr->dump();
return false;
} else {
return true;
}
}
void TraverseCXXMemberInitializers(
const clang::CXXConstructorDecl* constructor) {
// For constructors, we also need to create lifetimes for variables
// referenced by in-class member initializers; the visitor by default only
// visits expressions in the initializer list.
// We also need to associate member initializers with the members they
// initialize.
for (const auto* init : constructor->inits()) {
const auto* init_expr = init->getInit();
if (const auto* default_init =
clang::dyn_cast<clang::CXXDefaultInitExpr>(init_expr)) {
init_expr = default_init->getExpr();
}
if (init->getMember() && init->getMember()->getType()->isRecordType()) {
std::optional<const Object*> this_object =
object_repository_.GetThisObject();
assert(this_object.has_value());
const Object* field_object =
object_repository_.GetFieldObject(*this_object, init->getMember());
PropagateResultObject(init_expr, field_object);
} else if (init->getBaseClass()) {
std::optional<const Object*> this_object =
object_repository_.GetThisObject();
assert(this_object.has_value());
const Object* base_object = object_repository_.GetBaseClassObject(
*this_object, init->getBaseClass());
PropagateResultObject(init_expr, base_object);
}
// Traverse after finishing with the outer expression, including
// connecting the initializer (constructor) to its object.
TraverseStmt(const_cast<clang::Expr*>(init_expr));
}
}
ObjectRepository& object_repository_;
const FunctionLifetimesMap& callee_lifetimes_;
std::optional<std::string> error_;
};
llvm::Expected<ObjectRepository> ObjectRepository::Create(
const clang::FunctionDecl* func,
const llvm::DenseMap<const clang::FunctionDecl*, FunctionLifetimesOrError>&
callee_lifetimes) {
ObjectRepository object_repository;
const auto* method_decl = clang::dyn_cast<clang::CXXMethodDecl>(func);
const auto* definition = func->getDefinition();
assert(definition || (method_decl && method_decl->isPureVirtual()));
if (definition) func = definition;
object_repository.func_ = func;
object_repository.return_object_ = object_repository.CreateObject(
func->getReturnType(), Lifetime::CreateVariable(),
[](const clang::Expr*) { return Lifetime::CreateVariable(); });
if (method_decl) {
if (!method_decl->isStatic()) {
object_repository.this_object_ = object_repository.CreateObject(
method_decl->getFunctionObjectParameterType(),
Lifetime::CreateVariable(),
[](const clang::Expr*) { return Lifetime::CreateVariable(); });
}
}
VarDeclVisitor decl_visitor(object_repository, callee_lifetimes);
if (auto* constructor = clang::dyn_cast<clang::CXXConstructorDecl>(func)) {
decl_visitor.TraverseCXXMemberInitializers(constructor);
}
decl_visitor.TraverseFunctionDecl(const_cast<clang::FunctionDecl*>(func));
if (decl_visitor.error_.has_value()) {
return llvm::createStringError(llvm::inconvertibleErrorCode(),
*decl_visitor.error_);
}
return object_repository;
}
std::string ObjectRepository::DebugString() const {
std::string result;
llvm::raw_string_ostream os(result);
if (this_object_) {
os << "This " << (*this_object_)->DebugString() << "\n";
}
for (const auto& [decl, object] : object_repository_) {
os << decl->getDeclKindName() << " " << decl << " (";
decl->printName(os);
os << ") object: " << object->DebugString() << "\n";
}
for (const auto& [expr_i, object] : call_expr_args_objects_) {
const auto& [expr, i] = expr_i;
os << "Call " << expr << " (arg " << i
<< ") object: " << object->DebugString() << "\n";
}
for (const auto& [expr, object] : call_expr_this_pointers_) {
os << "Call " << expr << " (this) pointer: " << object->DebugString()
<< "\n";
}
os << "InitialPointsToMap:\n" << initial_points_to_map_.DebugString() << "\n";
for (const auto& [field, object] : field_object_map_) {
os << "Field '";
field.second->printName(os, field.second->getASTContext().getPrintingPolicy());
os << "' on " << field.first->DebugString()
<< " object: " << object->DebugString() << "\n";
}
for (const auto& [base, object] : base_object_map_) {
os << "Base of type " << clang::QualType(base.second, 0).getAsString()
<< " of " << base.first->DebugString()
<< " object: " << object->DebugString() << "\n";
}
os << "Return " << return_object_->DebugString() << "\n";
os.flush();
return result;
}
const Object* ObjectRepository::GetDeclObject(
const clang::ValueDecl* decl) const {
auto iter = object_repository_.find(decl);
if (iter == object_repository_.end()) {
llvm::errs() << "Didn't find object for Decl:\n";
decl->dump();
llvm::errs() << "\n" << DebugString();
llvm::report_fatal_error("Didn't find object for Decl");
}
return iter->second;
}
const Object* ObjectRepository::GetTemporaryObject(
const clang::MaterializeTemporaryExpr* expr) const {
auto iter = temporary_objects_.find(expr);
if (iter == temporary_objects_.end()) {
llvm::errs() << "Didn't find object for temporary expression:\n";
expr->dump();
llvm::errs() << "\n" << DebugString();
llvm::report_fatal_error("Didn't find object for temporary expression");
}
return iter->second;
}
const Object* ObjectRepository::GetOriginalParameterValue(
const clang::ParmVarDecl* var_decl) const {
auto iter = initial_parameter_object_.find(var_decl);
if (iter == initial_parameter_object_.end()) {
llvm::errs() << "Didn't find caller object for parameter:\n";
var_decl->dump();
llvm::errs() << "\n" << DebugString();
llvm::report_fatal_error("Didn't find caller object for parameter");
}
return iter->second;
}
FunctionLifetimes ObjectRepository::GetOriginalFunctionLifetimes() const {
FunctionLifetimes ret;
auto get_initial_lifetimes_or_die = [&](const Object* object) {
auto iter = initial_object_lifetimes_.find(object);
if (iter == initial_object_lifetimes_.end()) {
llvm::errs() << "Didn't find lifetimes for object "
<< object->DebugString();
llvm::report_fatal_error("Didn't find lifetimes for object");
}
return iter->second;
};
ret.return_lifetimes_ =
get_initial_lifetimes_or_die(GetReturnObject()).GetValueLifetimes();
if (this_object_.has_value()) {
ret.this_lifetimes_ = ValueLifetimes::PointerTo(
clang::dyn_cast<clang::CXXMethodDecl>(func_)->getThisType(),
get_initial_lifetimes_or_die(*this_object_));
}
ret.param_lifetimes_.reserve(func_->getNumParams());
for (size_t i = 0; i < func_->getNumParams(); i++) {
ret.param_lifetimes_.push_back(
get_initial_lifetimes_or_die(
GetOriginalParameterValue(func_->getParamDecl(i)))
.GetValueLifetimes());
}
if (!ret.IsValidForDecl(func_)) {
llvm::errs() << "Internal error: did not produce valid function lifetimes";
llvm::report_fatal_error(
"Internal error: did not produce valid function lifetimes");
}
return ret;
}
const Object* ObjectRepository::GetCallExprArgumentObject(
const clang::CallExpr* expr, size_t arg_index) const {
auto iter = call_expr_args_objects_.find(std::make_pair(expr, arg_index));
if (iter == call_expr_args_objects_.end()) {
llvm::errs() << "Didn't find object for argument " << arg_index
<< " of call:\n";
expr->dump();
llvm::errs() << "\n" << DebugString();
llvm::report_fatal_error("Didn't find object for argument");
}
return iter->second;
}
const Object* ObjectRepository::GetCallExprRetObject(
const clang::Expr* expr) const {
auto iter = call_expr_ret_objects_.find(expr);
if (iter == call_expr_ret_objects_.end()) {
llvm::errs() << "Didn't find object for return value of call:\n";
expr->dump();
llvm::errs() << "\n" << DebugString();
llvm::report_fatal_error("Didn't find object for return value");
}
return iter->second;
}
const FunctionLifetimes& ObjectRepository::GetCallExprVirtualLifetimes(
const clang::Expr* expr) const {
auto iter = call_expr_virtual_lifetimes_.find(expr);
if (iter == call_expr_virtual_lifetimes_.end()) {
llvm::errs() << "Didn't find object for return value of call:\n";
expr->dump();
llvm::errs() << "\n" << DebugString();
llvm::report_fatal_error("Didn't find object for return value");
}
return iter->second;
}
const Object* ObjectRepository::GetCallExprThisPointer(
const clang::CallExpr* expr) const {
auto iter = call_expr_this_pointers_.find(expr);
if (iter == call_expr_this_pointers_.end()) {
llvm::errs() << "Didn't find `this` object for call:\n";
expr->dump();
llvm::errs() << "\n" << DebugString();
llvm::report_fatal_error("Didn't find `this` object for call");
}
return iter->second;
}
const Object* ObjectRepository::GetCXXConstructExprArgumentObject(
const clang::CXXConstructExpr* expr, size_t arg_index) const {
auto iter = call_expr_args_objects_.find(std::make_pair(expr, arg_index));
if (iter == call_expr_args_objects_.end()) {
llvm::errs() << "Didn't find object for argument " << arg_index
<< " of constructor call:\n";
expr->dump();
llvm::errs() << "\n" << DebugString();
llvm::report_fatal_error(
"Didn't find object for argument of constructor call");
}
return iter->second;
}
const Object* ObjectRepository::GetCXXConstructExprThisPointer(
const clang::CXXConstructExpr* expr) const {
auto iter = call_expr_this_pointers_.find(expr);
if (iter == call_expr_this_pointers_.end()) {
llvm::errs() << "Didn't find `this` object for constructor:\n";
expr->dump();
llvm::errs() << "\n" << DebugString();
llvm::report_fatal_error("Didn't find `this` object for constructor");
}
return iter->second;
}
const Object* ObjectRepository::GetResultObject(
const clang::Expr* initializer_expr) const {
assert(clang::isa<clang::CXXConstructExpr>(initializer_expr) ||
clang::isa<clang::InitListExpr>(initializer_expr) ||
clang::isa<clang::CallExpr>(initializer_expr));
auto iter = result_objects_.find(initializer_expr);
if (iter == result_objects_.end()) {
llvm::errs() << "Didn't find result object for initializer:\n";
initializer_expr->dump();
llvm::errs() << "\n" << DebugString();
llvm::report_fatal_error("Didn't find result object for initializer");
}
return iter->second;
}
const Object* ObjectRepository::GetFieldObject(
const Object* struct_object, const clang::FieldDecl* field) const {
std::optional<const Object*> field_object =
GetFieldObjectInternal(struct_object, field);
if (!field_object.has_value()) {
llvm::errs() << "On an object of type "
<< struct_object->Type().getAsString()
<< ", trying to get field:\n";
field->dump();
llvm::errs() << "\n" << DebugString();
llvm::report_fatal_error("Didn't find field object");
}
return *field_object;
}
ObjectSet ObjectRepository::GetFieldObject(
const ObjectSet& struct_objects, const clang::FieldDecl* field) const {
ObjectSet ret;
for (const Object* object : struct_objects) {
ret.Add(GetFieldObject(object, field));
}
return ret;
}
const Object* ObjectRepository::GetBaseClassObject(
const Object* struct_object, const clang::Type* base) const {
base = base->getCanonicalTypeInternal().getTypePtr();
auto iter = base_object_map_.find(std::make_pair(struct_object, base));
if (iter == base_object_map_.end()) {
llvm::errs() << "On object " << struct_object->DebugString()
<< ", trying to get base:\n";
base->dump();
llvm::errs() << "\n" << DebugString();
llvm::report_fatal_error("Didn't find base object");
}
return iter->second;
}
ObjectSet ObjectRepository::GetBaseClassObject(const ObjectSet& struct_objects,
const clang::Type* base) const {
ObjectSet ret;
for (const Object* object : struct_objects) {
ret.Add(GetBaseClassObject(object, base));
}
return ret;
}
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 CallbackField, typename CallackBase>
void ForEachFieldAndBase(clang::QualType record_type,
const ObjectLifetimes& object_lifetimes,
const CallbackField& callback_field,
const CallackBase& callback_base) {
assert(record_type->isRecordType());
for (clang::FieldDecl* f :
record_type->getAs<clang::RecordType>()->getDecl()->fields()) {
ObjectLifetimes field_lifetimes = object_lifetimes.GetFieldOrBaseLifetimes(
f->getType(), GetFieldLifetimeArguments(f));
callback_field(field_lifetimes, f);
}
if (auto* cxxrecord = clang::dyn_cast<clang::CXXRecordDecl>(
record_type->getAs<clang::RecordType>()->getDecl())) {
for (const clang::CXXBaseSpecifier& base : cxxrecord->bases()) {
clang::QualType base_type = base.getType();
auto base_object_lifetimes = object_lifetimes.GetFieldOrBaseLifetimes(
base_type, GetLifetimeParameters(base_type));
callback_base(base_object_lifetimes, &*base_type.getCanonicalType());
ForEachFieldAndBase(base.getType(), base_object_lifetimes, callback_field,
callback_base);
}
}
}
} // namespace
class ObjectRepository::ObjectCreator {
public:
ObjectCreator(ObjectRepository& object_repository, PointsToMap& points_to_map)
: object_repository_(object_repository), points_to_map_(points_to_map) {}
const Object* CreateObjectsRecursively(
const ObjectLifetimes& object_lifetimes) {
if (auto it = object_cache_.find(object_lifetimes);
it != object_cache_.end()) {
return it->second;
}
const clang::QualType type = object_lifetimes.Type();
const ValueLifetimes& value_lifetimes =
object_lifetimes.GetValueLifetimes();
std::optional<FunctionLifetimes> function_lifetimes;
if (type->getAs<clang::FunctionType>()) {
function_lifetimes = value_lifetimes.GetFuncLifetimes();
}
const Object* obj = object_repository_.ConstructObject(
object_lifetimes.GetLifetime(), object_lifetimes.Type(),
function_lifetimes);
object_cache_[object_lifetimes] = obj;
object_repository_.initial_object_lifetimes_[obj] = object_lifetimes;
if (type->isIncompleteType()) {
// Nothing we can do.
return obj;
}
// Pointer type.
if (!PointeeType(type).isNull()) {
points_to_map_.ExtendPointerPointsToSet(
obj,
{CreateObjectsRecursively(value_lifetimes.GetPointeeLifetimes())});
return obj;
}
// Record type.
if (type->getAs<clang::RecordType>()) {
ForEachFieldAndBase(
type, object_lifetimes,
[this, obj](const ObjectLifetimes& field_lifetimes,
const clang::FieldDecl* f) {
const Object* field = CreateObjectsRecursively(field_lifetimes);
object_repository_.field_object_map_[std::make_pair(obj, f)] =
field;
},
[this, obj](const ObjectLifetimes& base_lifetimes,
const clang::Type* base_type) {
const Object* base_obj = CreateObjectsRecursively(base_lifetimes);
object_repository_
.base_object_map_[std::make_pair(obj, base_type)] = base_obj;
}
);
}
return obj;
}
private:
ObjectRepository& object_repository_;
PointsToMap& points_to_map_;
// We re-use the same Object for all the sub-objects with the same type and
// lifetimes. This avoids infinite loops in the case of structs like lists.
llvm::DenseMap<ObjectLifetimes, const Object*> object_cache_;
};
const Object* ObjectRepository::CreateObject(
const ObjectLifetimes& object_lifetimes, PointsToMap& points_to_map) {
ObjectCreator object_creator(*this, points_to_map);
return object_creator.CreateObjectsRecursively(object_lifetimes);
}
const Object* ObjectRepository::CreateObject(clang::QualType type,
Lifetime root_object_lifetime,
LifetimeFactory lifetime_factory) {
return CreateObject(
ObjectLifetimes(root_object_lifetime,
ValueLifetimes::Create(type, lifetime_factory).get()),
initial_points_to_map_);
}
template <typename... Args>
const Object* ObjectRepository::ConstructObject(Args&&... args) {
return new (object_allocator_.Allocate()) Object(args...);
}
// Clones an object and its base classes and fields, if any.
const Object* ObjectRepository::CloneObject(const Object* object) {
struct ObjectPair {
const Object* orig_object;
const Object* new_object;
};
auto clone = [this](const Object* obj) {
auto new_obj = ConstructObject(obj->GetLifetime(), obj->Type(),
obj->GetFuncLifetimes());
initial_points_to_map_.SetPointerPointsToSet(
new_obj, initial_points_to_map_.GetPointerPointsToSet(obj));
return new_obj;
};
const Object* new_root = clone(object);
initial_object_lifetimes_[new_root] = initial_object_lifetimes_[object];
std::vector<ObjectPair> object_stack{{object, new_root}};
while (!object_stack.empty()) {
auto [orig_object, new_object] = object_stack.back();
assert(orig_object->Type() == new_object->Type());
object_stack.pop_back();
auto record_type = orig_object->Type()->getAs<clang::RecordType>();
if (!record_type) {
continue;
}
// Base classes.
if (auto* cxxrecord =
clang::dyn_cast<clang::CXXRecordDecl>(record_type->getDecl())) {
for (const clang::CXXBaseSpecifier& base : cxxrecord->bases()) {
const Object* base_obj =
GetBaseClassObject(orig_object, base.getType());
const Object* new_base_obj = clone(base_obj);
base_object_map_[std::make_pair(
new_object, base.getType().getCanonicalType().getTypePtr())] =
new_base_obj;
object_stack.push_back(ObjectPair{base_obj, new_base_obj});
}
}
// Fields.
for (auto f : record_type->getDecl()->fields()) {
const Object* field_obj = GetFieldObject(orig_object, f);
const Object* new_field_obj = clone(field_obj);
field_object_map_[std::make_pair(new_object, f)] = new_field_obj;
object_stack.push_back(ObjectPair{field_obj, new_field_obj});
}
}
return new_root;
}
std::optional<const Object*> ObjectRepository::GetFieldObjectInternal(
const Object* struct_object, const clang::FieldDecl* field) const {
auto iter = field_object_map_.find(std::make_pair(struct_object, field));
if (iter != field_object_map_.end()) {
return iter->second;
}
if (auto* cxxrecord = clang::dyn_cast<clang::CXXRecordDecl>(
struct_object->Type()->getAs<clang::RecordType>()->getDecl())) {
for (const clang::CXXBaseSpecifier& base : cxxrecord->bases()) {
std::optional<const Object*> field_object = GetFieldObjectInternal(
GetBaseClassObject(struct_object, base.getType()), field);
if (field_object.has_value()) {
return field_object;
}
}
}
return std::nullopt;
}
} // namespace lifetimes
} // namespace tidy
} // namespace clang