| // 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_annotations/function_lifetimes.h" |
| |
| #include <algorithm> |
| #include <cassert> |
| #include <cstddef> |
| #include <functional> |
| #include <ostream> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_join.h" |
| #include "lifetime_annotations/lifetime.h" |
| #include "lifetime_annotations/lifetime_error.h" |
| #include "lifetime_annotations/lifetime_substitutions.h" |
| #include "lifetime_annotations/type_lifetimes.h" |
| #include "clang/AST/DeclCXX.h" |
| #include "clang/AST/Type.h" |
| #include "clang/AST/TypeLoc.h" |
| #include "clang/Basic/LLVM.h" |
| #include "llvm/ADT/DenseSet.h" |
| #include "llvm/ADT/SmallVector.h" |
| #include "llvm/Support/Error.h" |
| |
| namespace clang { |
| namespace tidy { |
| namespace lifetimes { |
| |
| llvm::Expected<FunctionLifetimes> FunctionLifetimes::CreateForDecl( |
| const clang::FunctionDecl* func, |
| const FunctionLifetimeFactory& lifetime_factory) { |
| clang::QualType this_type; |
| if (auto method = clang::dyn_cast<clang::CXXMethodDecl>(func); |
| method && !method->isStatic()) { |
| this_type = method->getThisType(); |
| } |
| clang::TypeLoc type_loc; |
| if (func->getTypeSourceInfo()) { |
| type_loc = func->getTypeSourceInfo()->getTypeLoc(); |
| } |
| return Create(func->getType()->getAs<clang::FunctionProtoType>(), type_loc, |
| this_type, lifetime_factory); |
| } |
| |
| llvm::Expected<FunctionLifetimes> FunctionLifetimes::CreateForFunctionType( |
| const clang::FunctionProtoType* func, clang::TypeLoc func_type_loc, |
| const FunctionLifetimeFactory& lifetime_factory) { |
| return Create(func, func_type_loc, clang::QualType(), lifetime_factory); |
| } |
| |
| llvm::Expected<FunctionLifetimes> FunctionLifetimes::CreateForFunctionType( |
| const clang::FunctionProtoType* func, |
| const FunctionLifetimeFactory& lifetime_factory) { |
| return CreateForFunctionType(func, clang::TypeLoc(), lifetime_factory); |
| } |
| |
| llvm::Expected<FunctionLifetimes> FunctionLifetimes::CreateCopy( |
| const LifetimeFactory& factory) const { |
| FunctionLifetimes ret; |
| |
| if (this_lifetimes_.has_value()) { |
| ValueLifetimes tmp; |
| if (llvm::Error err = |
| ValueLifetimes::Create(this_lifetimes_->Type(), factory) |
| .moveInto(tmp)) { |
| return std::move(err); |
| } |
| ret.this_lifetimes_ = std::move(tmp); |
| } |
| |
| ret.param_lifetimes_.reserve(param_lifetimes_.size()); |
| for (size_t i = 0; i < param_lifetimes_.size(); i++) { |
| ValueLifetimes tmp; |
| if (llvm::Error err = |
| ValueLifetimes::Create(param_lifetimes_[i].Type(), factory) |
| .moveInto(tmp)) { |
| return std::move(err); |
| } |
| ret.param_lifetimes_.push_back(std::move(tmp)); |
| } |
| |
| if (llvm::Error err = |
| ValueLifetimes::Create(return_lifetimes_.Type(), factory) |
| .moveInto(ret.return_lifetimes_)) { |
| return std::move(err); |
| } |
| |
| return ret; |
| } |
| |
| FunctionLifetimes FunctionLifetimes::ForOverriddenMethod( |
| const clang::CXXMethodDecl* method) { |
| FunctionLifetimes ret = *this; |
| assert(this_lifetimes_.has_value()); |
| ret.this_lifetimes_ = ValueLifetimes::ForPointerLikeType( |
| method->getThisType(), |
| this_lifetimes_.value().GetPointeeLifetimes().GetFieldOrBaseLifetimes( |
| method->getFunctionObjectParameterType(), {})); |
| assert(ret.IsValidForDecl(method)); |
| return ret; |
| } |
| |
| bool FunctionLifetimes::IsValidForDecl(const clang::FunctionDecl* function) { |
| // TODO(veluca): also validate the types of the arguments, and/or the type of |
| // the function itself. |
| if (auto method = clang::dyn_cast<clang::CXXMethodDecl>(function); |
| method && !method->isStatic()) { |
| if (!this_lifetimes_.has_value()) return false; |
| } |
| return param_lifetimes_.size() == function->param_size(); |
| } |
| |
| llvm::Expected<FunctionLifetimes> FunctionLifetimes::Create( |
| const clang::FunctionProtoType* type, clang::TypeLoc type_loc, |
| const clang::QualType this_type, |
| const FunctionLifetimeFactory& lifetime_factory) { |
| FunctionLifetimes ret; |
| |
| llvm::SmallVector<const clang::Attr*> attrs; |
| if (type_loc) { |
| StripAttributes(type_loc, attrs); |
| } |
| llvm::SmallVector<const clang::Expr*> lifetime_names; |
| if (llvm::Error err = GetAttributeLifetimes(attrs).moveInto(lifetime_names)) { |
| return std::move(err); |
| } |
| |
| if (this_type.isNull() && !lifetime_names.empty()) { |
| return llvm::make_error<LifetimeError>( |
| LifetimeError::Type::Other, |
| absl::StrCat("Encountered a `this` lifetime on a function with no " |
| "`this` parameter")); |
| } |
| |
| if (!this_type.isNull()) { |
| ValueLifetimes tmp; |
| const clang::Expr* lifetime_name = nullptr; |
| if (!lifetime_names.empty()) { |
| if (lifetime_names.size() != 1) { |
| return llvm::make_error<LifetimeError>( |
| LifetimeError::Type::Other, |
| absl::StrCat("Expected a single lifetime but ", |
| lifetime_names.size(), " were given")); |
| } |
| lifetime_name = lifetime_names.front(); |
| } |
| if (llvm::Error err = |
| lifetime_factory.CreateThisLifetimes(this_type, lifetime_name) |
| .moveInto(tmp)) { |
| return std::move(err); |
| } |
| ret.this_lifetimes_ = std::move(tmp); |
| } |
| |
| clang::FunctionTypeLoc func_type_loc; |
| if (type_loc) { |
| func_type_loc = type_loc.getAsAdjusted<clang::FunctionTypeLoc>(); |
| } |
| ret.param_lifetimes_.reserve(type->getNumParams()); |
| for (size_t i = 0; i < type->getNumParams(); i++) { |
| clang::TypeLoc param_type_loc; |
| if (func_type_loc) { |
| const clang::ParmVarDecl* param = func_type_loc.getParam(i); |
| if (param && param->getTypeSourceInfo()) { |
| param_type_loc = param->getTypeSourceInfo()->getTypeLoc(); |
| } |
| } |
| ValueLifetimes tmp; |
| if (llvm::Error err = |
| lifetime_factory |
| .CreateParamLifetimes(type->getParamType(i), param_type_loc) |
| .moveInto(tmp)) { |
| return std::move(err); |
| } |
| ret.param_lifetimes_.push_back(std::move(tmp)); |
| } |
| |
| clang::TypeLoc return_type_loc; |
| if (func_type_loc) { |
| return_type_loc = func_type_loc.getReturnLoc(); |
| } |
| if (llvm::Error err = |
| lifetime_factory |
| .CreateReturnLifetimes(type->getReturnType(), return_type_loc, |
| ret.param_lifetimes_, ret.this_lifetimes_) |
| .moveInto(ret.return_lifetimes_)) { |
| return std::move(err); |
| } |
| |
| return ret; |
| } |
| |
| bool FunctionLifetimes::HasAny( |
| const std::function<bool(Lifetime)>& predicate) const { |
| return std::any_of(param_lifetimes_.begin(), param_lifetimes_.end(), |
| [&predicate](const ValueLifetimes& v) { |
| return v.HasAny(predicate); |
| }) || |
| return_lifetimes_.HasAny(predicate) || |
| (this_lifetimes_.has_value() && this_lifetimes_->HasAny(predicate)); |
| } |
| |
| llvm::DenseSet<Lifetime> FunctionLifetimes::AllFreeLifetimes() const { |
| // TODO(veluca): this is incorrect in the presence of HRTBs. |
| llvm::DenseSet<Lifetime> all_lifetimes; |
| Traverse([&all_lifetimes](Lifetime l, Variance) { |
| if (l != Lifetime::Static()) { |
| all_lifetimes.insert(l); |
| } |
| }); |
| return all_lifetimes; |
| } |
| |
| void FunctionLifetimes::SubstituteLifetimes( |
| const LifetimeSubstitutions& subst) { |
| // TODO(veluca): this is incorrect in the presence of HRTBs. |
| std::for_each(param_lifetimes_.begin(), param_lifetimes_.end(), |
| [&subst](ValueLifetimes& v) { v.SubstituteLifetimes(subst); }); |
| return_lifetimes_.SubstituteLifetimes(subst); |
| if (this_lifetimes_.has_value()) { |
| this_lifetimes_->SubstituteLifetimes(subst); |
| } |
| } |
| |
| void FunctionLifetimes::Traverse( |
| std::function<void(Lifetime&, Variance)> visitor) { |
| for (auto& param : param_lifetimes_) { |
| param.Traverse(visitor); |
| } |
| return_lifetimes_.Traverse(visitor); |
| if (this_lifetimes_.has_value()) { |
| this_lifetimes_->Traverse(visitor); |
| } |
| } |
| |
| void FunctionLifetimes::Traverse( |
| std::function<void(const Lifetime&, Variance)> visitor) const { |
| const_cast<FunctionLifetimes*>(this)->Traverse( |
| [visitor](Lifetime& l, Variance v) { visitor(l, v); }); |
| } |
| |
| std::string FunctionLifetimes::DebugString(LifetimeFormatter formatter) const { |
| std::vector<std::string> formatted_param_lifetimes; |
| |
| // Add parentheses to non-trivial nested lifetimes, i.e. fn parameters with >1 |
| // lifetimes, as their DebugString does not contain parentheses. |
| auto maybe_add_parentheses = [&](std::string s) { |
| if (s.find_first_of(",()") != std::string::npos || s.empty()) { |
| // However, don't add redundant parentheses. |
| if (!(s.size() >= 2 && s.front() == '(' && s.back() == ')')) { |
| return absl::StrCat("(", s, ")"); |
| } |
| } |
| return s; |
| }; |
| |
| for (const auto& param : param_lifetimes_) { |
| formatted_param_lifetimes.push_back( |
| maybe_add_parentheses(param.DebugString(formatter))); |
| } |
| |
| std::string result; |
| if (this_lifetimes_.has_value()) { |
| result = absl::StrCat( |
| maybe_add_parentheses(this_lifetimes_->DebugString(formatter)), ":"); |
| } |
| if (!result.empty() && !formatted_param_lifetimes.empty()) { |
| absl::StrAppend(&result, " "); |
| } |
| absl::StrAppend(&result, absl::StrJoin(formatted_param_lifetimes, ", ")); |
| |
| if (return_lifetimes_.HasLifetimes()) { |
| if (!result.empty()) { |
| absl::StrAppend(&result, " "); |
| } |
| absl::StrAppend( |
| &result, "-> ", |
| maybe_add_parentheses(return_lifetimes_.DebugString(formatter))); |
| } |
| |
| return result; |
| } |
| |
| std::ostream& operator<<(std::ostream& os, |
| const FunctionLifetimes& func_lifetimes) { |
| return os << func_lifetimes.DebugString(); |
| } |
| |
| } // namespace lifetimes |
| } // namespace tidy |
| } // namespace clang |