| // 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/lifetime_annotations.h" | |
| #include <cassert> | |
| #include <functional> | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <utility> | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/string_view.h" | |
| #include "lifetime_annotations/function_lifetimes.h" | |
| #include "lifetime_annotations/lifetime.h" | |
| #include "lifetime_annotations/lifetime_error.h" | |
| #include "lifetime_annotations/lifetime_symbol_table.h" | |
| #include "lifetime_annotations/pointee_type.h" | |
| #include "lifetime_annotations/type_lifetimes.h" | |
| #include "clang/AST/APValue.h" | |
| #include "clang/AST/ASTContext.h" | |
| #include "clang/AST/Attr.h" | |
| #include "clang/AST/Attrs.inc" | |
| #include "clang/AST/Decl.h" | |
| #include "clang/AST/DeclCXX.h" | |
| #include "clang/AST/DeclarationName.h" | |
| #include "clang/AST/PrettyPrinter.h" | |
| #include "clang/AST/Type.h" | |
| #include "clang/AST/TypeLoc.h" | |
| #include "clang/Basic/LLVM.h" | |
| #include "clang/Basic/LangOptions.h" | |
| #include "clang/Basic/SourceLocation.h" | |
| #include "clang/Lex/Lexer.h" | |
| #include "clang/Lex/Pragma.h" | |
| #include "clang/Lex/Preprocessor.h" | |
| #include "llvm/ADT/DenseSet.h" | |
| #include "llvm/ADT/SmallVector.h" | |
| #include "llvm/ADT/StringRef.h" | |
| #include "llvm/Support/Error.h" | |
| namespace clang { | |
| namespace tidy { | |
| namespace lifetimes { | |
| namespace { | |
| llvm::Expected<FunctionLifetimes> ParseLifetimeAnnotations( | |
| const clang::FunctionDecl* func, LifetimeSymbolTable& symbol_table, | |
| const std::string& lifetimes_str) { | |
| clang::LangOptions lang_opts; | |
| clang::Lexer lexer(clang::SourceLocation(), lang_opts, lifetimes_str.data(), | |
| lifetimes_str.data(), | |
| lifetimes_str.data() + lifetimes_str.size()); | |
| const char* end = lifetimes_str.data() + lifetimes_str.size(); | |
| auto tok = [&lexer, &lifetimes_str, end]() -> llvm::StringRef { | |
| clang::Token token; | |
| if (lexer.getBufferLocation() != end) { | |
| lexer.LexFromRawLexer(token); | |
| return llvm::StringRef( | |
| lifetimes_str.data() + token.getLocation().getRawEncoding(), | |
| token.getLength()); | |
| } | |
| return ""; | |
| }; | |
| // TODO(veluca): this is too permissive. | |
| auto next_lifetime = [&]() { | |
| llvm::StringRef next = tok(); | |
| while (next == "(" || next == ")" || next == "," || next == "->" || | |
| next == ":" || next == "[" || next == "]" || next == ">" || | |
| next == "<") { | |
| next = tok(); | |
| } | |
| return next; | |
| }; | |
| FunctionLifetimeFactorySingleCallback factory( | |
| [&symbol_table, | |
| &next_lifetime](const clang::Expr*) -> llvm::Expected<Lifetime> { | |
| llvm::StringRef next = next_lifetime(); | |
| if (next.empty()) { | |
| return llvm::make_error<LifetimeError>( | |
| LifetimeError::Type::Other, | |
| "Invalid lifetime annotation: too few lifetimes"); | |
| } | |
| return symbol_table.LookupNameAndMaybeDeclare(next); | |
| }); | |
| auto ret = FunctionLifetimes::CreateForDecl(func, factory); | |
| if (!next_lifetime().empty()) { | |
| return llvm::make_error<LifetimeError>( | |
| LifetimeError::Type::Other, | |
| "Invalid lifetime annotation: too many lifetimes"); | |
| } | |
| return ret; | |
| } | |
| // Parse a "(a, b): (a, b), (), a -> b"-style annotation into a | |
| // FunctionLifetimes. | |
| // TODO(veluca): this is a temporary solution. | |
| llvm::Expected<FunctionLifetimes> ParseLifetimeAnnotations( | |
| const clang::FunctionDecl* func, LifetimeSymbolTable& symbol_table, | |
| const clang::AnnotateAttr* attr) { | |
| auto error = [func](absl::string_view detail = absl::string_view()) { | |
| std::string msg = absl::StrCat("Invalid lifetime annotation for function ", | |
| func->getNameAsString()); | |
| if (!detail.empty()) { | |
| absl::StrAppend(&msg, ": ", detail); | |
| } | |
| return llvm::make_error<LifetimeError>(LifetimeError::Type::Other, msg); | |
| }; | |
| if (attr->args_size() != 1) { | |
| return error("`lifetimes` attribute must have exactly one argument"); | |
| } | |
| // The lexer requires a null character at the end of the string, so copy it to | |
| // a std::string to guarantee this. | |
| llvm::StringRef lifetimes; | |
| if (llvm::Error err = | |
| EvaluateAsStringLiteral(*attr->args_begin(), func->getASTContext()) | |
| .moveInto(lifetimes)) { | |
| return error(toString(std::move(err))); | |
| } | |
| return ParseLifetimeAnnotations(func, symbol_table, lifetimes.str()); | |
| } | |
| llvm::Expected<FunctionLifetimes> GetLifetimeAnnotationsInternal( | |
| const clang::FunctionDecl* func, LifetimeSymbolTable& symbol_table, | |
| bool elision_enabled) { | |
| const clang::AnnotateAttr* lifetime_annotation = nullptr; | |
| for (const clang::Attr* attr : func->attrs()) { | |
| if (auto annotate = clang::dyn_cast<clang::AnnotateAttr>(attr)) { | |
| if (annotate->getAnnotation() == "lifetimes") { | |
| if (lifetime_annotation != nullptr) { | |
| return llvm::make_error<LifetimeError>( | |
| LifetimeError::Type::Other, | |
| absl::StrCat("Can't extract lifetimes as '", | |
| func->getNameAsString(), | |
| "' has multiple lifetime annotations")); | |
| } | |
| lifetime_annotation = annotate; | |
| } | |
| } | |
| } | |
| if (lifetime_annotation) { | |
| return ParseLifetimeAnnotations(func, symbol_table, lifetime_annotation); | |
| } | |
| class Factory : public FunctionLifetimeFactory { | |
| public: | |
| Factory(bool elision_enabled, const clang::FunctionDecl* func, | |
| LifetimeSymbolTable& symbol_table) | |
| : elision_enabled_(elision_enabled), | |
| func_(func), | |
| symbol_table_(symbol_table) {} | |
| private: | |
| llvm::Expected<Lifetime> LifetimeFromName(const clang::Expr* name) const { | |
| llvm::StringRef name_str; | |
| if (llvm::Error err = | |
| EvaluateAsStringLiteral(name, func_->getASTContext()) | |
| .moveInto(name_str)) { | |
| return std::move(err); | |
| } | |
| return symbol_table_.LookupNameAndMaybeDeclare(name_str); | |
| } | |
| LifetimeFactory ParamLifetimeFactory() const { | |
| return [this](const clang::Expr* name) -> llvm::Expected<Lifetime> { | |
| if (name) { | |
| Lifetime lifetime; | |
| if (llvm::Error err = LifetimeFromName(name).moveInto(lifetime)) { | |
| return std::move(err); | |
| } | |
| return lifetime; | |
| } | |
| // As a special-case, lifetime is always inferred for the `this` | |
| // parameter for destructors. The obvious lifetime is definitionally | |
| // correct in this case: the object must be valid for the duration | |
| // of the call, or else the behavior is undefined. So we can infer | |
| // safely even if elision is disabled. | |
| if (!elision_enabled_ && | |
| func_->getDeclName().getNameKind() != | |
| clang::DeclarationName::CXXDestructorName) { | |
| return llvm::make_error<LifetimeError>( | |
| LifetimeError::Type::ElisionNotEnabled, | |
| absl::StrCat("Lifetime elision not enabled for '", | |
| func_->getNameAsString(), "'")); | |
| } | |
| Lifetime lifetime = Lifetime::CreateVariable(); | |
| symbol_table_.LookupLifetimeAndMaybeDeclare(lifetime); | |
| return lifetime; | |
| }; | |
| } | |
| llvm::Expected<ValueLifetimes> CreateThisLifetimes( | |
| clang::QualType type, const clang::Expr* lifetime_name) const override { | |
| LifetimeFactory lifetime_factory = ParamLifetimeFactory(); | |
| clang::QualType pointee_type = PointeeType(type); | |
| assert(!pointee_type.isNull()); | |
| ValueLifetimes value_lifetimes; | |
| if (llvm::Error err = | |
| ValueLifetimes::Create(pointee_type, clang::TypeLoc(), | |
| lifetime_factory) | |
| .moveInto(value_lifetimes)) { | |
| return std::move(err); | |
| } | |
| Lifetime object_lifetime; | |
| if (llvm::Error err = | |
| lifetime_factory(lifetime_name).moveInto(object_lifetime)) { | |
| return std::move(err); | |
| } | |
| return ValueLifetimes::ForPointerLikeType( | |
| type, ObjectLifetimes(object_lifetime, value_lifetimes)); | |
| } | |
| llvm::Expected<ValueLifetimes> CreateParamLifetimes( | |
| clang::QualType param_type, | |
| clang::TypeLoc param_type_loc) const override { | |
| return ValueLifetimes::Create(param_type, param_type_loc, | |
| ParamLifetimeFactory()); | |
| } | |
| static std::optional<Lifetime> GetSingleInputLifetime( | |
| const llvm::SmallVector<ValueLifetimes>& param_lifetimes, | |
| const std::optional<ValueLifetimes>& this_lifetimes) { | |
| // If we have an implicit `this` parameter, its lifetime is assigned to | |
| // all lifetimes in the return type. | |
| if (this_lifetimes.has_value()) { | |
| return this_lifetimes->GetPointeeLifetimes().GetLifetime(); | |
| } | |
| llvm::DenseSet<Lifetime> all_input_lifetimes; | |
| for (const ValueLifetimes& v : param_lifetimes) { | |
| // Function pointers and function references have an implied static | |
| // lifetime, but this shouldn't count as an input lifetime for the | |
| // purposes of lifetime elision. | |
| const ValueLifetimes& lifetimes_to_traverse = | |
| v.Type()->isFunctionPointerType() || | |
| v.Type()->isFunctionReferenceType() | |
| ? v.GetPointeeLifetimes().GetValueLifetimes() | |
| : v; | |
| lifetimes_to_traverse.Traverse( | |
| [&all_input_lifetimes](Lifetime l, Variance) { | |
| all_input_lifetimes.insert(l); | |
| }); | |
| } | |
| if (all_input_lifetimes.size() == 1) { | |
| // If we have a single input lifetime, its lifetime is assigned to all | |
| // output lifetimes. | |
| return *all_input_lifetimes.begin(); | |
| } else { | |
| // Otherwise, we don't know how to elide the output lifetime. | |
| return std::nullopt; | |
| } | |
| } | |
| llvm::Expected<ValueLifetimes> CreateReturnLifetimes( | |
| clang::QualType return_type, clang::TypeLoc return_type_loc, | |
| const llvm::SmallVector<ValueLifetimes>& param_lifetimes, | |
| const std::optional<ValueLifetimes>& this_lifetimes) const override { | |
| // TODO(veluca): adapt to lifetime elision for function pointers. | |
| std::optional<Lifetime> input_lifetime = | |
| GetSingleInputLifetime(param_lifetimes, this_lifetimes); | |
| return ValueLifetimes::Create( | |
| return_type, return_type_loc, | |
| [&input_lifetime, | |
| this](const clang::Expr* name) -> llvm::Expected<Lifetime> { | |
| if (name) { | |
| Lifetime lifetime; | |
| if (llvm::Error err = LifetimeFromName(name).moveInto(lifetime)) { | |
| return std::move(err); | |
| } | |
| return lifetime; | |
| } | |
| if (!elision_enabled_) { | |
| return llvm::make_error<LifetimeError>( | |
| LifetimeError::Type::ElisionNotEnabled, | |
| absl::StrCat("Lifetime elision not enabled for '", | |
| func_->getNameAsString(), "'")); | |
| } | |
| // If we have a single input lifetime, its lifetime is assigned to | |
| // all output lifetimes. | |
| if (input_lifetime.has_value()) { | |
| return *input_lifetime; | |
| } else { | |
| // Otherwise, we don't know how to elide the output lifetime. | |
| return llvm::make_error<LifetimeError>( | |
| LifetimeError::Type::CannotElideOutputLifetimes, | |
| absl::StrCat("Cannot elide output lifetimes for '", | |
| func_->getNameAsString(), | |
| "' because it is a non-member function that " | |
| "does not have " | |
| "exactly one input lifetime")); | |
| } | |
| }); | |
| } | |
| bool elision_enabled_; | |
| const clang::FunctionDecl* func_; | |
| LifetimeSymbolTable& symbol_table_; | |
| }; | |
| Factory factory(elision_enabled, func, symbol_table); | |
| return FunctionLifetimes::CreateForDecl(func, factory); | |
| } | |
| } // namespace | |
| char LifetimeError::ID; | |
| llvm::Expected<FunctionLifetimes> GetLifetimeAnnotations( | |
| const clang::FunctionDecl* func, const LifetimeAnnotationContext& context, | |
| LifetimeSymbolTable* symbol_table) { | |
| // TODO(mboehme): if we have multiple declarations of a function, make sure | |
| // they are all annotated with the same lifetimes. | |
| clang::SourceManager& source_manager = | |
| func->getASTContext().getSourceManager(); | |
| clang::FileID file_id = | |
| source_manager.getFileID(func->getSourceRange().getBegin()); | |
| bool elision_enabled = context.lifetime_elision_files.contains(file_id); | |
| LifetimeSymbolTable throw_away_symbol_table; | |
| if (!symbol_table) { | |
| symbol_table = &throw_away_symbol_table; | |
| } | |
| return GetLifetimeAnnotationsInternal(func, *symbol_table, elision_enabled); | |
| } | |
| llvm::Expected<FunctionLifetimes> ParseLifetimeAnnotations( | |
| const clang::FunctionDecl* func, const std::string& lifetimes_str, | |
| LifetimeSymbolTable* symbol_table) { | |
| LifetimeSymbolTable throw_away_symbol_table; | |
| if (!symbol_table) { | |
| symbol_table = &throw_away_symbol_table; | |
| } | |
| return ParseLifetimeAnnotations(func, *symbol_table, lifetimes_str); | |
| } | |
| namespace { | |
| class LifetimeElisionPragmaHandler : public clang::PragmaHandler { | |
| public: | |
| explicit LifetimeElisionPragmaHandler( | |
| std::shared_ptr<LifetimeAnnotationContext> context) | |
| : clang::PragmaHandler("lifetime_elision"), context_(context) {} | |
| void HandlePragma(clang::Preprocessor& preprocessor, | |
| clang::PragmaIntroducer introducer, | |
| clang::Token&) override { | |
| clang::SourceManager& source_manager = preprocessor.getSourceManager(); | |
| clang::FileID file_id = source_manager.getFileID(introducer.Loc); | |
| context_->lifetime_elision_files.insert(file_id); | |
| } | |
| private: | |
| std::shared_ptr<LifetimeAnnotationContext> context_; | |
| }; | |
| } // namespace | |
| void AddLifetimeAnnotationHandlers( | |
| clang::Preprocessor& preprocessor, | |
| std::shared_ptr<LifetimeAnnotationContext> context) { | |
| // Preprocessor takes ownership of the handler. | |
| preprocessor.AddPragmaHandler("clang", | |
| new LifetimeElisionPragmaHandler(context)); | |
| } | |
| } // namespace lifetimes | |
| } // namespace tidy | |
| } // namespace clang |