| // 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 <functional> |
| #include <optional> |
| #include <string> |
| |
| #include "absl/base/attributes.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/Attrs.inc" |
| #include "clang/AST/Decl.h" |
| #include "clang/AST/DeclBase.h" |
| #include "clang/AST/DeclTemplate.h" |
| #include "clang/AST/Expr.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, |
| std::function<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) { |
| 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 or string literals.")); |
| }; |
| |
| switch (value1.getKind()) { |
| case clang::APValue::Int: |
| if (value1.getInt() != value2.getInt()) { |
| return InconsistentAnnotationsError(annotation_name); |
| } |
| 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) { |
| 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(); |
| if (int_value.isZero()) { |
| return false; |
| } else { |
| // Non-zero values are treated as true. |
| return true; |
| } |
| } |
| |
| 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::string>> GetAnnotationWithStringArg( |
| 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; |
| if (args.size() != 1) { |
| return absl::InvalidArgumentError( |
| absl::StrCat("Annotation ", annotation_name, |
| " must have a single string argument.")); |
| } |
| absl::StatusOr<absl::string_view> arg = |
| GetExprAsStringLiteral(*args[0], decl.getASTContext()); |
| if (!arg.ok()) { |
| return absl::InvalidArgumentError( |
| absl::StrCat("Annotation ", annotation_name, |
| " must have a single string argument.")); |
| } |
| return std::string(*arg); |
| } |
| |
| } // namespace crubit |