blob: 3ecc62b40578132ab0c635ea1efa91f9082dfd24 [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
#ifndef CRUBIT_LIFETIME_ANNOTATIONS_TYPE_LIFETIMES_H_
#define CRUBIT_LIFETIME_ANNOTATIONS_TYPE_LIFETIMES_H_
#include <cassert>
#include <cstddef>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "lifetime_annotations/lifetime.h"
#include "lifetime_annotations/lifetime_substitutions.h"
#include "lifetime_annotations/lifetime_symbol_table.h"
#include "clang/AST/Type.h"
#include "clang/AST/TypeLoc.h"
#include "clang/AST/TypeOrdering.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseMapInfo.h"
#include "llvm/ADT/Hashing.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
namespace clang {
namespace tidy {
namespace lifetimes {
// Returns a lifetime in some human-readable format.
using LifetimeFormatter = std::function<std::string(Lifetime)>;
// Variance of a reference-like type with respect to the type it refers to.
enum Variance {
kCovariant,
kInvariant,
};
// Strips any attributes off `type` and returns the result.
clang::QualType StripAttributes(clang::QualType type);
// Strips any attributes off `type_loc` and returns the result. The stripped
// attributes are added to `attrs`.
clang::TypeLoc StripAttributes(clang::TypeLoc type_loc,
llvm::SmallVector<const clang::Attr*>& attrs);
// Extracts and returns the arguments of any `annotate_type("lifetime", ...)`
// attributes in `attrs`.
// If `attrs` contains multiple such attributes, the arguments from all of
// these attributes are concatenated.
llvm::Expected<llvm::SmallVector<const clang::Expr*>> GetAttributeLifetimes(
llvm::ArrayRef<const clang::Attr*> attrs);
// Extracts the lifetime parameters of the given type.
llvm::SmallVector<std::string> GetLifetimeParameters(clang::QualType type);
// The parameter, if non-null, is the name that the lifetime was annotated with.
// This is provided as an `Expr` that is expected to evaluate to a string
// literal, rather than an actual string, as callers of the `LifetimeFactory`
// may not have access to an `ASTContext`, which is needed to evaluate the
// expression.
using LifetimeFactory =
std::function<llvm::Expected<Lifetime>(const clang::Expr*)>;
class ObjectLifetimes;
class FunctionLifetimes;
// Represents the lifetimes of a value; these may be 0 for non-reference-like
// types, 1 for pointers/references, and an arbitrary number for structs with
// template arguments/lifetime parameters.
class ValueLifetimes {
public:
// Creates an invalid ValueLifetimes, which should not be used. This is
// provided only for usage with functions with output parameters.
ValueLifetimes() : ValueLifetimes(clang::QualType()) {}
ValueLifetimes(const ValueLifetimes& other);
ValueLifetimes& operator=(const ValueLifetimes& other);
~ValueLifetimes();
// Creates a ValueLifetimes for a *value* of a given type.
// Only fails if lifetime_factory fails.
// Lifetimes will be created in post-order in the tree of lifetimes.
// If a non-null `type_loc` is passed in, passes any lifetime names
// found in `annotate_type` attributes on to `lifetime_factory`.
// Note: It's necessary to pass in both `type` and `type_loc` because we may
// not be able to traverse as deeply into `type_loc` as we can into `type`.
// For example, if the type is a typedef for a pointer type, we can traverse
// into that pointer type through `type`, but not through `type_loc`. This is
// because the pointer type is not spelled out in the place where the
// `type_loc` appears in the source code.
static llvm::Expected<ValueLifetimes> Create(
clang::QualType type, clang::TypeLoc type_loc,
LifetimeFactory lifetime_factory);
static llvm::Expected<ValueLifetimes> Create(
clang::QualType type, LifetimeFactory lifetime_factory) {
return Create(type, clang::TypeLoc(), lifetime_factory);
}
// Creates a ValueLifetimes for a pointer to a given ObjectLifetimes.
static ValueLifetimes PointerTo(clang::QualType pointer_type,
const ObjectLifetimes& object);
// Returns a ValueLifetimes for a lifetime-less type.
// `type` must not be a pointer-like type or a record type.
static ValueLifetimes ForLifetimeLessType(clang::QualType type);
// Returns a ValueLifetimes for a pointer-like type that points to an object
// having lifetimes `object_lifetimes`.
static ValueLifetimes ForPointerLikeType(
clang::QualType type, const ObjectLifetimes& object_lifetimes);
// Returns a ValueLifetimes for a record type. If the record type has template
// parameters, pass the corresponding template argument lifetimes in
// `template_argument_lifetimes`; otherwise, pass an empty vector.
// Similarly, pass any lifetime parameters in `lifetime_parameters`.
// The structure of `template_argument_lifetimes` is analogous to that of the
// return value of `GetTemplateArgs()`.
static ValueLifetimes ForRecord(
clang::QualType type,
std::vector<std::vector<std::optional<ValueLifetimes>>>
template_argument_lifetimes,
LifetimeSymbolTable lifetime_parameters);
std::string DebugString(const LifetimeFormatter& formatter = [](Lifetime l) {
return l.DebugString();
}) const;
// Returns the type of the value.
clang::QualType Type() const { return type_; }
// Returns the ObjectLifetimes of the pointed-to object. Type() must be a
// pointer or reference type.
const ObjectLifetimes& GetPointeeLifetimes() const;
// Returns the FunctionLifetimes of the callable that this object represents.
// Type() must be a FunctionProtoType.
const FunctionLifetimes& GetFuncLifetimes() const;
// Returns the lifetimes of the i-th template argument at the given nesting
// `depth` within a chain of nested templates and with the given `index`.
// For example, for a type `Outer<int*, double*>::Inner<long*>`, the
// `double*` template argument has depth 0 and index 1.
const std::optional<ValueLifetimes>& GetTemplateArgumentLifetimes(
size_t depth, size_t index) const {
assert(type_->isRecordType());
return template_argument_lifetimes_.at(depth).at(index);
}
// Returns the number of template nesting levels.
size_t GetNumTemplateNestingLevels() const {
assert(type_->isRecordType());
return template_argument_lifetimes_.size();
}
// Returns the number of template arguments at a given nesting `depth` (see
// `GetTemplateArgumentLifetimes` for details).
size_t GetNumTemplateArgumentsAtDepth(size_t depth) const {
assert(type_->isRecordType());
return template_argument_lifetimes_.at(depth).size();
}
// Returns the lifetime associated with the given named lifetime parameter.
Lifetime GetLifetimeParameter(llvm::StringRef param) const {
std::optional<Lifetime> ret =
lifetime_parameters_by_name_.LookupName(param);
assert(ret.has_value());
return ret.value();
}
bool HasLifetimes() const {
return pointee_lifetimes_ != nullptr || function_lifetimes_ != nullptr ||
!lifetime_parameters_by_name_.GetMapping().empty() ||
!template_argument_lifetimes_.empty();
}
// Returns true if `predicate` returns true for any lifetime that appears in
// the `ValueLifetimes`.
bool HasAny(const std::function<bool(Lifetime)>& predicate) const;
// Applies `subst` to all lifetimes in this ValueLifetimes.
// See FunctionLifetimes::SubstituteLifetimes() for details.
void SubstituteLifetimes(const LifetimeSubstitutions& subst);
// Traverses all the lifetimes in the object, recursively. The
// visit is done in post-order on the lifetime tree of this type.
// The callback may mutate the lifetime in an arbitrary way; `variance` will
// be set to signal the variance of the lifetime in the position it appears
// in; this is decided with the `variance` parameter to Traverse, which
// signals the variance of the current Value. Note that the visitor may be
// called multiple times with the same lifetime (but in different positions).
// TODO(veluca): verify that the handling of variance is correct in all cases.
void Traverse(std::function<void(Lifetime&, Variance)> visitor,
Variance variance = kCovariant);
void Traverse(std::function<void(const Lifetime&, Variance)> visitor,
Variance variance = kCovariant) const;
private:
explicit ValueLifetimes(clang::QualType type);
// Note: only one of `pointee_lifetimes_`, `function_lifetimes_` or
// `template_argument_lifetimes_` is non-empty.
std::unique_ptr<ObjectLifetimes> pointee_lifetimes_;
std::unique_ptr<FunctionLifetimes> function_lifetimes_;
std::vector<std::vector<std::optional<ValueLifetimes>>>
template_argument_lifetimes_;
clang::QualType type_;
// Tracks the mapping from the names of the lifetimes on the struct/class
// definition to the associated `Lifetime`s. For example, in the following
// code
//
// class string_view LIFETIME_PARAM(d) { ... };
//
// string_view $a drop_last(string_view $a in) {
// string_view result;
// ...
// return result;
// }
//
// the value stored in `result`/`in` has 1 lifetime argument. This lifetime
// has a local name "a" (it is not possible to retrieve this mapping from this
// ValueLifetimes object). This lifetime substitutes lifetime "d" from
// string_view (this mapping is tracked by lifetime_parameters_by_name_).
LifetimeSymbolTable lifetime_parameters_by_name_;
friend class llvm::DenseMapInfo<clang::tidy::lifetimes::ValueLifetimes>;
};
// Represents all the lifetimes of an object. The object itself always has
// a lifetime; in addition, there may be lifetimes associated with the value
// of the object.
class ObjectLifetimes {
public:
// Creates an invalid ObjectLifetimes, which should not be used. This is
// provided only for usage with functions with output parameters.
ObjectLifetimes() = default;
ObjectLifetimes(Lifetime lifetime, ValueLifetimes value_lifetimes)
: lifetime_(lifetime), value_lifetimes_(value_lifetimes) {}
// Returns the lifetime of the object itself.
Lifetime GetLifetime() const { return lifetime_; }
// Returns the lifetime of the contained value.
const ValueLifetimes& GetValueLifetimes() const { return value_lifetimes_; }
clang::QualType Type() const { return value_lifetimes_.Type(); }
std::string DebugString(const LifetimeFormatter& formatter = [](Lifetime l) {
return l.DebugString();
}) const;
// Returns the ObjectLifetimes for a base class or a field of the given type.
ObjectLifetimes GetFieldOrBaseLifetimes(
clang::QualType type,
llvm::SmallVector<std::string> type_lifetime_args) const;
// Returns true if `predicate` returns true for any lifetime that appears in
// the `ObjectLifetimes`.
bool HasAny(const std::function<bool(Lifetime)>& predicate) const;
// Applies `subst` to all lifetimes in this ObjectLifetimes.
// See FunctionLifetimes::SubstituteLifetimes() for details.
void SubstituteLifetimes(const LifetimeSubstitutions& subst);
// Traverses all the lifetimes in the object, recursively. The
// visit is done in post-order on the lifetime tree of this type.
// The callback may mutate the lifetime in an arbitrary way; `variance` will
// be set to signal the variance of the lifetime in the position it appears
// in; this is decided with the `variance` parameter to Traverse, which
// signals the variance of the current Object. Note that the visitor may be
// called multiple times with the same lifetime (but in different positions).
// `indirection_type` defines the type of the pointer (or reference) to this
// object; this is used to determine variance of its pointees.
// TODO(veluca): verify that the handling of variance is correct in all cases.
void Traverse(std::function<void(Lifetime&, Variance)> visitor,
Variance variance = kCovariant,
clang::QualType indirection_type = clang::QualType());
void Traverse(std::function<void(const Lifetime&, Variance)> visitor,
Variance variance = kCovariant,
clang::QualType indirection_type = clang::QualType()) const;
private:
// Returns the ObjectLifetimes for an object of a given type, whose lifetimes
// are scoped within or derived from the object that `this`
// represents, i.e. it is a field or a base class of the object.
// `value_lifetimes_.Type()` must be a record type (class, struct or union).
// TODO(veluca): ideally, `type_lifetime_args` should not be needed, but
// rather extracted from attributes on the `type`.
ObjectLifetimes GetObjectLifetimesForTypeInContext(
clang::QualType type, llvm::SmallVector<std::string> type_lifetime_args,
llvm::StringRef object_lifetime_parameter) const;
friend class llvm::DenseMapInfo<clang::tidy::lifetimes::ObjectLifetimes>;
Lifetime lifetime_;
ValueLifetimes value_lifetimes_;
};
// TODO(lukasza): Try deduplicating GetTemplateArgs(QualType) vs
// GetTemplateArgs(TypeLoc).
// Return type: The outer vector is indexed by the "depth" of the template
// argument within a chain of nested templates; the inner vector contains the
// template arguments at a given depth.
// For example, for a type `Outer<int*, double*>::Inner<long*>`, this returns
// (in pseudo-code) { { int*, double* }, { long* } };
llvm::SmallVector<llvm::ArrayRef<clang::TemplateArgument>> GetTemplateArgs(
clang::QualType type);
// Returns any template arguments present on `type_loc`.
// - If `type_loc` does not have template arguments, returns an empty vector.
// - If the type has template arguments but it is not possible to retrieve
// `TypeLocs` for them, returns std::nullopt. For example, this can happen if
// `type_loc` is a `TypedefTypeLoc`: There is no source location for those
// template arguments at the location of the `type_loc`.
// Return type: See description for `GetTemplateArgs()`.
// This helper function is placed here to be able to share it with clang-tidy.
std::optional<llvm::SmallVector<llvm::SmallVector<clang::TypeLoc>>>
GetTemplateArgs(clang::TypeLoc type_loc);
// Evaluate the given expression as a string literal. Returns an error if the
// expression is not a string literal.
// This is exposed here so that it can be used in other places that need to
// evaluate string literal arguments of `annotate` attributes.
llvm::Expected<llvm::StringRef> EvaluateAsStringLiteral(
const clang::Expr* expr, const clang::ASTContext& ast_context);
} // namespace lifetimes
} // namespace tidy
} // namespace clang
namespace llvm {
template <>
struct DenseMapInfo<clang::tidy::lifetimes::ValueLifetimes> {
static clang::tidy::lifetimes::ValueLifetimes getEmptyKey() {
return clang::tidy::lifetimes::ValueLifetimes(
DenseMapInfo<clang::QualType>().getEmptyKey());
}
static clang::tidy::lifetimes::ValueLifetimes getTombstoneKey() {
return clang::tidy::lifetimes::ValueLifetimes(
DenseMapInfo<clang::QualType>().getTombstoneKey());
}
static bool isEqual(const clang::tidy::lifetimes::ValueLifetimes& lhs,
const clang::tidy::lifetimes::ValueLifetimes& rhs);
static unsigned getHashValue(
const clang::tidy::lifetimes::ValueLifetimes& value_lifetimes);
};
template <>
struct DenseMapInfo<clang::tidy::lifetimes::ObjectLifetimes> {
static clang::tidy::lifetimes::ObjectLifetimes getEmptyKey() {
return clang::tidy::lifetimes::ObjectLifetimes(
DenseMapInfo<clang::tidy::lifetimes::Lifetime>().getEmptyKey(),
DenseMapInfo<clang::tidy::lifetimes::ValueLifetimes>().getEmptyKey());
}
static clang::tidy::lifetimes::ObjectLifetimes getTombstoneKey() {
return clang::tidy::lifetimes::ObjectLifetimes(
DenseMapInfo<clang::tidy::lifetimes::Lifetime>().getTombstoneKey(),
DenseMapInfo<clang::tidy::lifetimes::ValueLifetimes>()
.getTombstoneKey());
}
static bool isEqual(const clang::tidy::lifetimes::ObjectLifetimes& lhs,
const clang::tidy::lifetimes::ObjectLifetimes& rhs) {
return DenseMapInfo<clang::tidy::lifetimes::Lifetime>::isEqual(
lhs.lifetime_, rhs.lifetime_) &&
DenseMapInfo<clang::tidy::lifetimes::ValueLifetimes>::isEqual(
lhs.value_lifetimes_, rhs.value_lifetimes_);
}
static unsigned getHashValue(
const clang::tidy::lifetimes::ObjectLifetimes& object_lifetimes) {
unsigned hash =
DenseMapInfo<clang::tidy::lifetimes::Lifetime>::getHashValue(
object_lifetimes.lifetime_);
return hash_combine(
hash,
DenseMapInfo<clang::tidy::lifetimes::ValueLifetimes>::getHashValue(
object_lifetimes.value_lifetimes_));
}
};
} // namespace llvm
#endif // CRUBIT_LIFETIME_ANNOTATIONS_TYPE_LIFETIMES_H_