| // 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 "common/annotation_reader.h" | |
| #include <optional> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/base/attributes.h" | |
| #include "absl/base/nullability.h" | |
| #include "absl/functional/function_ref.h" | |
| #include "absl/status/status.h" | |
| #include "absl/status/statusor.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/string_view.h" | |
| #include "common/status_macros.h" | |
| #include "common/string_view_conversion.h" | |
| #include "clang/AST/APValue.h" | |
| #include "clang/AST/ASTContext.h" | |
| #include "clang/AST/Attr.h" | |
| #include "clang/AST/Decl.h" | |
| #include "clang/AST/DeclBase.h" | |
| #include "clang/AST/DeclTemplate.h" | |
| #include "clang/AST/Expr.h" | |
| #include "clang/AST/TypeBase.h" | |
| #include "clang/Basic/AttrKinds.h" | |
| #include "clang/Basic/LLVM.h" | |
| #include "llvm/ADT/APSInt.h" | |
| #include "llvm/ADT/StringRef.h" | |
| namespace crubit { | |
| namespace { | |
| // Returns the string literal value of `expr`. | |
| // | |
| // `ast_context` is the associated `ASTContext`. If `expr` is not a string | |
| // literal, `error()` is returned. | |
| // | |
| // As `StringLiteral` expressions are always allocated on the `ASTContext (see | |
| // `StringLiteral::Create()`), the returned `string_view` has the same lifetime | |
| // as `ast_context`. | |
| // | |
| // TODO(yongheng): Merge with lifetime_annotations/type_lifetimes.cc. | |
| static absl::StatusOr<absl::string_view> GetExprAsStringLiteral( | |
| const clang::Expr& expr, | |
| const clang::ASTContext& ast_context ABSL_ATTRIBUTE_LIFETIME_BOUND, | |
| absl::FunctionRef<absl::Status()> error) { | |
| clang::Expr::EvalResult eval_result; | |
| if (!expr.EvaluateAsConstantExpr(eval_result, ast_context) || | |
| !eval_result.Val.isLValue()) { | |
| return error(); | |
| } | |
| const auto* eval_result_expr = | |
| eval_result.Val.getLValueBase().dyn_cast<const clang::Expr*>(); | |
| if (!eval_result_expr) { | |
| return error(); | |
| } | |
| const auto* string_literal = | |
| clang::dyn_cast<clang::StringLiteral>(eval_result_expr); | |
| if (!string_literal) { | |
| return error(); | |
| } | |
| return StringViewFromStringRef(string_literal->getString()); | |
| } | |
| // Returns the `AnnotateAttr` with the given `annotation_name` if it exists. If | |
| // there are multiple annotations with the given name, returns an error. | |
| // | |
| // For example, given the following C++ code: | |
| // | |
| // class [[clang::annotate("crubit_annotation_foo", "bar")]] MyClass {} | |
| // ... | |
| // }; | |
| // | |
| // GetAnnotateAttrSingleDecl(my_class_decl, "crubit_annotation_foo") will | |
| // return the `AnnotateAttr` with the annotation `crubit_annotation_foo`, | |
| // from which the "bar" argument can be extracted. | |
| static absl::StatusOr<const clang::AnnotateAttr*> GetAnnotateAttrSingleDecl( | |
| const clang::Decl& decl, absl::string_view annotation_name) { | |
| const clang::AnnotateAttr* found_attr = nullptr; | |
| for (clang::AnnotateAttr* attr : decl.specific_attrs<clang::AnnotateAttr>()) { | |
| if (attr->getAnnotation() != llvm::StringRef(annotation_name)) continue; | |
| if (found_attr != nullptr) { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Only one `", annotation_name, | |
| "` annotation may be placed on a declaration.")); | |
| } | |
| found_attr = attr; | |
| } | |
| return found_attr; | |
| } | |
| static absl::Status InconsistentAnnotationsError( | |
| absl::string_view annotation_name) { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Different declarations have inconsistent `", | |
| annotation_name, "` annotations.")); | |
| } | |
| static absl::Status CheckExpressionsAreSameConstant( | |
| const clang::Expr& expr1, const clang::Expr& expr2, | |
| absl::string_view annotation_name, const clang::ASTContext& ast_context) { | |
| if (!ast_context.hasSameType(expr1.getType(), expr2.getType())) { | |
| return InconsistentAnnotationsError(annotation_name); | |
| } | |
| clang::Expr::EvalResult eval_result_1, eval_result_2; | |
| if (!expr1.EvaluateAsConstantExpr(eval_result_1, ast_context) || | |
| !expr2.EvaluateAsConstantExpr(eval_result_2, ast_context)) { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Arguments of `", annotation_name, | |
| "` annotation must be constant expressions.")); | |
| } | |
| const clang::APValue& value1 = eval_result_1.Val; | |
| const clang::APValue& value2 = eval_result_2.Val; | |
| if (value1.getKind() != value2.getKind()) { | |
| return InconsistentAnnotationsError(annotation_name); | |
| } | |
| auto must_be_int_or_string_error = [annotation_name]() { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Arguments of `", annotation_name, | |
| "` annotation must be of integral type, string literal, " | |
| "or constructor.")); | |
| }; | |
| switch (value1.getKind()) { | |
| case clang::APValue::Int: | |
| if (value1.getInt() != value2.getInt()) { | |
| return InconsistentAnnotationsError(annotation_name); | |
| } | |
| break; | |
| case clang::APValue::Struct: | |
| // Structs only appear when we use them for their type info, they | |
| // are all empty though. | |
| break; | |
| case clang::APValue::LValue: { | |
| CRUBIT_ASSIGN_OR_RETURN( | |
| absl::string_view value1_string, | |
| GetExprAsStringLiteral(expr1, ast_context, | |
| must_be_int_or_string_error)); | |
| CRUBIT_ASSIGN_OR_RETURN( | |
| absl::string_view value2_string, | |
| GetExprAsStringLiteral(expr2, ast_context, | |
| must_be_int_or_string_error)); | |
| if (value1_string != value2_string) { | |
| return InconsistentAnnotationsError(annotation_name); | |
| } | |
| break; | |
| } | |
| default: | |
| return must_be_int_or_string_error(); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| static absl::Status CheckAnnotationsConsistent( | |
| const clang::AnnotateAttr* annotate1, const clang::AnnotateAttr* annotate2, | |
| const clang::ASTContext& ast_context) { | |
| if (annotate1->args_size() != annotate2->args_size()) | |
| return InconsistentAnnotationsError( | |
| StringViewFromStringRef(annotate1->getAnnotation())); | |
| for (int i = 0; i < annotate1->args_size(); ++i) { | |
| CRUBIT_RETURN_IF_ERROR(CheckExpressionsAreSameConstant( | |
| *annotate1->args_begin()[i], *annotate2->args_begin()[i], | |
| StringViewFromStringRef(annotate1->getAnnotation()), ast_context)); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| // Returns the `clang::Decl` that should be used for reading annotations. | |
| // | |
| // Template declarations technically do not have annotations-- the | |
| // *templated* decl has annotations. So, if we're searching for annotations | |
| // on a template decl, we should search for annotations on the templated | |
| // decl instead. | |
| static const clang::Decl& DeclForAnnotations(const clang::Decl& decl) { | |
| auto* template_decl = clang::dyn_cast<clang::TemplateDecl>(&decl); | |
| if (template_decl == nullptr) { | |
| return decl; | |
| } | |
| auto* templated_decl = template_decl->getTemplatedDecl(); | |
| if (templated_decl == nullptr) { | |
| return decl; | |
| } | |
| return *templated_decl; | |
| } | |
| } // namespace | |
| absl::StatusOr<bool> GetExprAsBool(const clang::Expr& expr, | |
| const clang::ASTContext& ast_context) { | |
| if (!expr.getType()->isBooleanType()) { | |
| return absl::InvalidArgumentError( | |
| "annotation expression must evaluate to a bool"); | |
| } | |
| clang::Expr::EvalResult eval_result; | |
| if (!expr.EvaluateAsConstantExpr(eval_result, ast_context)) { | |
| return absl::InvalidArgumentError( | |
| "failed to evaluate annotation expression as a constant"); | |
| } | |
| if (eval_result.Val.getKind() != clang::APValue::Int) { | |
| return absl::InvalidArgumentError( | |
| "annotation expression must evaluate to a bool"); | |
| } | |
| const llvm::APSInt& int_value = eval_result.Val.getInt(); | |
| return !int_value.isZero(); | |
| } | |
| absl::StatusOr<absl::string_view> GetExprAsStringLiteral( | |
| const clang::Expr& expr, | |
| const clang::ASTContext& ast_context ABSL_ATTRIBUTE_LIFETIME_BOUND) { | |
| return GetExprAsStringLiteral(expr, ast_context, []() { | |
| return absl::InvalidArgumentError( | |
| "cannot evaluate argument as a string literal"); | |
| }); | |
| } | |
| absl::StatusOr<std::optional<AnnotateArgs>> GetAnnotateAttrArgs( | |
| const clang::Decl& decl, absl::string_view annotation_name) { | |
| const clang::AnnotateAttr* found_attr = nullptr; | |
| int num_found = 0; | |
| for (const clang::Decl* redecl_ptr : decl.redecls()) { | |
| if (redecl_ptr == nullptr) continue; | |
| const clang::Decl& redecl = DeclForAnnotations(*redecl_ptr); | |
| CRUBIT_ASSIGN_OR_RETURN(const clang::AnnotateAttr* attr, | |
| GetAnnotateAttrSingleDecl(redecl, annotation_name)); | |
| if (attr != nullptr) { | |
| ++num_found; | |
| if (found_attr == nullptr) { | |
| found_attr = attr; | |
| } else { | |
| CRUBIT_RETURN_IF_ERROR( | |
| CheckAnnotationsConsistent(found_attr, attr, decl.getASTContext())); | |
| } | |
| } | |
| } | |
| // If only one redeclaration had an annotation, check the annotation against | |
| // itself. This checks that all arguments have the expected type. | |
| if (num_found == 1) { | |
| CRUBIT_RETURN_IF_ERROR(CheckAnnotationsConsistent( | |
| found_attr, found_attr, DeclForAnnotations(decl).getASTContext())); | |
| } | |
| if (found_attr == nullptr) { | |
| return std::nullopt; | |
| } | |
| return AnnotateArgs(found_attr->args_begin(), found_attr->args_end()); | |
| } | |
| absl::StatusOr<bool> HasAnnotationWithoutArgs(const clang::Decl& decl, | |
| absl::string_view annotation) { | |
| absl::StatusOr<std::optional<AnnotateArgs>> maybe_args = | |
| GetAnnotateAttrArgs(decl, annotation); | |
| if (!maybe_args.ok()) { | |
| return maybe_args.status(); | |
| } | |
| if (!maybe_args->has_value()) { | |
| return false; | |
| } | |
| if (!maybe_args->value().empty()) { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Annotation ", annotation, " does not expect arguments.")); | |
| } | |
| return true; | |
| } | |
| absl::StatusOr<std::optional<std::vector<std::string>>> | |
| GetAnnotationWithStringArgs(const clang::Decl& decl, | |
| absl::string_view annotation_name) { | |
| CRUBIT_ASSIGN_OR_RETURN(std::optional<AnnotateArgs> maybe_args, | |
| GetAnnotateAttrArgs(decl, annotation_name)); | |
| if (!maybe_args.has_value()) { | |
| return std::nullopt; | |
| } | |
| const AnnotateArgs& args = *maybe_args; | |
| std::vector<std::string> result; | |
| result.reserve(args.size()); | |
| for (const clang::Expr* arg_expr : args) { | |
| absl::StatusOr<absl::string_view> arg = | |
| GetExprAsStringLiteral(*arg_expr, decl.getASTContext()); | |
| if (!arg.ok()) { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Annotation ", annotation_name, | |
| " arguments must be string literals.")); | |
| } | |
| result.push_back(std::string(*arg)); | |
| } | |
| return result; | |
| } | |
| absl::StatusOr<std::optional<std::string>> GetAnnotationWithStringArg( | |
| const clang::Decl& decl, absl::string_view annotation_name) { | |
| CRUBIT_ASSIGN_OR_RETURN(std::optional<std::vector<std::string>> maybe_args, | |
| GetAnnotationWithStringArgs(decl, annotation_name)); | |
| if (!maybe_args.has_value()) { | |
| return std::nullopt; | |
| } | |
| if (maybe_args->size() != 1) { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Annotation ", annotation_name, | |
| " must have a single string argument.")); | |
| } | |
| return std::move(maybe_args->front()); | |
| } | |
| absl::StatusOr<const clang::AnnotateTypeAttr* absl_nullable> | |
| GetTypeAnnotationSingleDecl(const clang::Type* absl_nonnull type | |
| ABSL_ATTRIBUTE_LIFETIME_BOUND, | |
| absl::string_view annotation_name) { | |
| const clang::Type* current_type = type; | |
| const clang::AnnotateTypeAttr* found_attr = nullptr; | |
| while (const auto* attributed_type = | |
| current_type->getAs<clang::AttributedType>()) { | |
| if (attributed_type->getAttrKind() == clang::attr::AnnotateType) { | |
| const clang::AnnotateTypeAttr* annotateAttr = | |
| clang::cast<clang::AnnotateTypeAttr>(attributed_type->getAttr()); | |
| if (annotateAttr->getAnnotation() == llvm::StringRef(annotation_name)) { | |
| if (found_attr != nullptr) { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Only one `", annotation_name, | |
| "` annotation may be placed on a type.")); | |
| } | |
| found_attr = annotateAttr; | |
| } | |
| } | |
| current_type = attributed_type->getEquivalentType().getTypePtr(); | |
| } | |
| return found_attr; | |
| } | |
| } // namespace crubit |