| /* |
| * Copyright 2016 The Kythe Authors. All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "kythe/cxx/indexer/cxx/marked_source.h" |
| |
| #include "clang/AST/DeclCXX.h" |
| #include "clang/AST/DeclVisitor.h" |
| #include "clang/AST/PrettyPrinter.h" |
| #include "clang/Format/Format.h" |
| #include "clang/Rewrite/Core/Rewriter.h" |
| #include "clang/Sema/Template.h" |
| #include "gflags/gflags.h" |
| #include "google/protobuf/stubs/common.h" |
| #include "kythe/cxx/indexer/cxx/clang_utils.h" |
| |
| DEFINE_bool(reformat_marked_source, false, |
| "Reformat source code used in MarkedSource (experimental)."); |
| DEFINE_bool(pretty_print_function_prototypes, false, |
| "Synthesize new function prototypes (experimental)."); |
| |
| namespace kythe { |
| namespace { |
| /// \return true if `range` is valid for use in annotations. |
| bool IsValidRange(const clang::SourceManager& source_manager, |
| const clang::SourceRange& range) { |
| // Check that the range is valid and ends in macros xnor files. |
| if (!range.isValid() || |
| range.getBegin().isFileID() != range.getEnd().isFileID()) { |
| return false; |
| } |
| // Reject definitions in macro expansions or that span multiple files. |
| if (!range.getBegin().isFileID() || |
| source_manager.getFileID(range.getBegin()) != |
| source_manager.getFileID(range.getEnd())) { |
| return false; |
| } |
| return true; |
| } |
| |
| llvm::StringRef GetTextRange(const clang::SourceManager& source_manager, |
| const clang::SourceRange& range) { |
| if (!IsValidRange(source_manager, range)) { |
| return llvm::StringRef(); |
| } |
| const char* begin = source_manager.getCharacterData(range.getBegin()); |
| const char* end = source_manager.getCharacterData(range.getEnd()); |
| if (begin > end) { |
| return llvm::StringRef(); |
| } |
| return llvm::StringRef(begin, end - begin); |
| } |
| |
| /// \brief The filename to use to refer to code being formatted. |
| constexpr char kReplacementFile[] = "x.cc"; |
| |
| /// \brief Reformats `source_text`. |
| /// \param replacements the set of transformations applied to `source_text`. |
| /// \param incomplete set to true if reformatting failed. |
| /// \return the reformatted text buffer (or the empty string). |
| std::string Reformat(const clang::LangOptions& lang_options, |
| llvm::StringRef source_text, |
| clang::tooling::Replacements* replacements, |
| bool* incomplete) { |
| clang::format::FormatStyle style = |
| clang::format::getGoogleStyle(clang::format::FormatStyle::LK_Cpp); |
| std::vector<clang::tooling::Range> ranges = { |
| clang::tooling::Range(0, source_text.size())}; |
| *replacements = clang::format::reformat(style, source_text, ranges, |
| kReplacementFile, incomplete); |
| if (*incomplete) { |
| return ""; |
| } |
| llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem( |
| new llvm::vfs::InMemoryFileSystem); |
| clang::FileManager Files(clang::FileSystemOptions(), InMemoryFileSystem); |
| clang::DiagnosticsEngine Diagnostics( |
| llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs>(new clang::DiagnosticIDs), |
| new clang::DiagnosticOptions); |
| clang::SourceManager Sources(Diagnostics, Files); |
| auto Source = llvm::MemoryBuffer::getMemBuffer(source_text); |
| InMemoryFileSystem->addFileNoOwn(kReplacementFile, 0, Source.get()); |
| clang::FileID ID = |
| Sources.createFileID(Files.getFile(kReplacementFile), |
| clang::SourceLocation(), clang::SrcMgr::C_User); |
| clang::Rewriter Rewrite(Sources, lang_options); |
| clang::tooling::applyAllReplacements(*replacements, Rewrite); |
| std::string result_string; |
| { |
| llvm::raw_string_ostream result_stream(result_string); |
| Rewrite.getEditBuffer(ID).write(result_stream); |
| } |
| return result_string; |
| } |
| |
| /// \brief A span of source text with some attached properties. |
| struct Annotation { |
| enum Kind : unsigned char { |
| TokenText, |
| ArgListWithParens, |
| Type, |
| QualifiedName |
| }; |
| Kind kind; |
| size_t begin; |
| size_t end; |
| bool operator<(const Annotation& o) const { |
| return std::tie(begin, o.end, kind) < std::tie(o.begin, end, o.kind); |
| } |
| }; |
| |
| /// \brief Used to manage the process of building `MarkedSource` trees. |
| class NodeStack { |
| public: |
| /// \brief Copy data from `annotations` and `formatted_range` to |
| /// `dest_source`. |
| /// \return the MarkedSource node covering an identifier, or null. |
| MarkedSource* ProcessAnnotations(const std::string& formatted_range, |
| const std::vector<Annotation>& annotations, |
| MarkedSource* dest_source) { |
| /// For certain kinds of annotations, we'll substitute our own special |
| /// MarkedSource. When we enter one of these, cancel_count gets |
| /// incremented; when we exit, it gets decremented. |
| size_t cancel_count = 0; |
| MarkedSource* ident_node = nullptr; |
| size_t cursor = 0; |
| // There's always at least one annotation. It spans the whole of |
| // formatted_range_ and it's ordered before the other annotations. |
| size_t annotation = 0; |
| for (;;) { |
| size_t next_begin = (annotation == annotations.size()) |
| ? formatted_range.size() |
| : annotations[annotation].begin; |
| while (!nodes_.empty() && nodes_.top().annotation->end <= next_begin) { |
| if (cursor < nodes_.top().annotation->end) { |
| AppendToTop(formatted_range, cancel_count, cursor, |
| nodes_.top().annotation->end, true); |
| cursor = nodes_.top().annotation->end; |
| } |
| if (nodes_.top().annotation->kind == Annotation::QualifiedName || |
| nodes_.top().annotation->kind == Annotation::ArgListWithParens) { |
| --cancel_count; |
| } |
| nodes_.pop(); |
| } |
| if (annotation == annotations.size()) { |
| break; |
| } |
| const auto& next = annotations[annotation++]; |
| if (cursor < next_begin) { |
| AppendToTop(formatted_range, cancel_count, cursor, next_begin, false); |
| cursor = next_begin; |
| } |
| nodes_.push(Node{&next, annotation == 1 |
| ? dest_source |
| : nodes_.top().marked_source->add_child()}); |
| auto* child = nodes_.top().marked_source; |
| switch (next.kind) { |
| case Annotation::TokenText: |
| break; |
| case Annotation::ArgListWithParens: { |
| child->set_kind(MarkedSource::PARAMETER_LOOKUP_BY_PARAM); |
| child->set_pre_text("("); |
| child->set_post_child_text(", "); |
| child->set_post_text(")"); |
| ++cancel_count; |
| break; |
| } |
| case Annotation::Type: |
| child->set_kind(MarkedSource::TYPE); |
| break; |
| case Annotation::QualifiedName: |
| child->set_kind(MarkedSource::BOX); |
| ident_node = child; |
| ++cancel_count; |
| break; |
| } |
| } |
| return ident_node; |
| } |
| |
| private: |
| /// \brief called to append raw text to the annotation span on the top of |
| /// the node stack. |
| /// |
| /// This will happen in two cases: |
| /// - there is text between the current cursor position and the end of |
| /// the current span, and that span is going to be popped from the stack |
| /// immediately after append_to_top finishes (at_end is true). |
| /// No more children will be added to this span before it's popped. |
| /// - there is text between the current cursor position and the start |
| /// of the next span, and that next span will be pushed to the stack |
| /// immediately after append_to_top finishes (at_end is false). |
| /// |
| /// \param formatted_range the formatted text range |
| /// \param cancel_count the number of cancelling annotations we're |
| /// underneath |
| /// \param start the start offset in the formatted text range |
| /// \param end the end offset (exclusive) in the formatted text range |
| /// \param at_end whether the span on the top of the stack is about to be |
| /// popped because there are no other spans that get opened before the |
| /// current span closes. |
| void AppendToTop(const std::string& formatted_range, size_t cancel_count, |
| size_t start, size_t end, bool at_end) { |
| CHECK(!nodes_.empty()); |
| if (cancel_count != 0) { |
| return; |
| } |
| const auto* annotation = nodes_.top().annotation; |
| auto* node = nodes_.top().marked_source; |
| if (at_end) { |
| if (node->child().empty() && node->post_text().empty()) { |
| node->mutable_pre_text()->append(formatted_range, start, end - start); |
| } else { |
| node->mutable_post_text()->append(formatted_range, start, end - start); |
| } |
| } else { |
| // If there are children before this node, we need to add a new BOX |
| // to hold this token. |
| if (node->child().empty()) { |
| node->mutable_pre_text()->append(formatted_range, start, end - start); |
| } else { |
| auto* new_node = node->add_child(); |
| new_node->mutable_pre_text()->append(formatted_range, start, |
| end - start); |
| } |
| } |
| } |
| /// A currently-entered annotation. |
| struct Node { |
| const Annotation* annotation; |
| MarkedSource* marked_source; |
| }; |
| /// The stack of currently entered annotations and their corresponding |
| /// MarkedSource messages. |
| std::stack<Node> nodes_; |
| }; |
| |
| /// \brief Try to get a return type range for a function that returns a function |
| /// pointer. |
| /// |
| /// getReturnTypeSourceRange doesn't work for functions that return |
| /// pointers to functions. This makes some sense, given that these look |
| /// like this: |
| /// float (*bam(short function_arg))(int function_ptr_arg) |
| /// We still want to grovel through the type to pick the right bit out |
| /// of, e.g.: |
| /// virtual float (*bam(short function_arg) const)(int function_ptr_arg) = 0; |
| clang::SourceRange GetReturnTypeSourceRangeForFunctionPointerReturningFunction( |
| const clang::FunctionDecl* decl) { |
| if (const auto* type_info = decl->getTypeSourceInfo()) { |
| if (auto function_type = type_info->getTypeLoc() |
| .IgnoreParens() |
| .getAs<clang::FunctionTypeLoc>()) { |
| if (auto return_loc = function_type.getReturnLoc()) { |
| if (auto as_ptr = |
| return_loc.IgnoreParens().getAs<clang::PointerTypeLoc>()) { |
| if (auto as_fn = as_ptr.getPointeeLoc() |
| .IgnoreParens() |
| .getAs<clang::FunctionTypeLoc>()) { |
| return as_fn.getSourceRange(); |
| } |
| } |
| } |
| } |
| } |
| return {}; |
| } |
| bool SameFileRangesOverlapOpenInterval(const clang::SourceRange& outer, |
| const clang::SourceRange& inner) { |
| return outer.isValid() && inner.isValid() && |
| outer.getBegin().getRawEncoding() <= |
| inner.getBegin().getRawEncoding() && |
| outer.getEnd().getRawEncoding() > inner.getBegin().getRawEncoding(); |
| } |
| /// \brief Walks the AST to annotate source text. |
| /// |
| /// The AST refers to source locations in an un-reformatted buffer, so we need |
| /// to transform them (using `replacements`) to refer to offsets in the |
| /// reformatted buffer. |
| class DeclAnnotator : public clang::DeclVisitor<DeclAnnotator> { |
| public: |
| DeclAnnotator(MarkedSourceCache* cache, |
| clang::tooling::Replacements* replacements, |
| clang::SourceLocation original_begin, |
| const std::string& formatted_range, MarkedSource* marked_source, |
| const clang::SourceRange& default_name_range) |
| : cache_(cache), |
| replacements_(replacements), |
| original_begin_(original_begin), |
| formatted_range_(formatted_range), |
| marked_source_(marked_source), |
| name_range_(default_name_range) { |
| // Make it invariant that we should always be inside an annotation. |
| annotations_.push_back( |
| Annotation{Annotation::TokenText, 0, formatted_range.size()}); |
| // Remember the location of the qualified name. |
| InsertAnnotation(default_name_range, Annotation{Annotation::QualifiedName}); |
| } |
| MarkedSource* ident_node() { |
| return ident_node_ ? ident_node_ : marked_source_; |
| } |
| // \brief Insert one or more type annotations. |
| // \param type_range the source range covering the type. |
| // \param arg_list the source range covering the argument list for functions, |
| // or an invalid range otherwise. |
| // |
| // This function will split the type annotation range if the identifier for |
| // the decl being annotated is inside the range (e.g., char x[] = {1};). |
| void InsertTypeAnnotation(const clang::SourceRange& type_range, |
| clang::SourceRange arg_list) { |
| clang::SourceRange type_lhs = type_range; |
| clang::SourceRange type_rhs = type_lhs; |
| // By default, we assume that nested ranges imply nested annotation nodes. |
| // If we come across (e.g.) a function returning a function pointer, this |
| // will give suboptimal results. We'll instead split the type range if we |
| // find that it contains parameters or the decl name. |
| // |
| // TODO(zarko): Even if we perform this split, we'll end up with a |
| // type signature for |
| // virtual float (*bam(short a) const)(int b) = 0; |
| // that looks like `float (* const)(int b)` |
| // There are other places this can happen too, like: |
| // float (*bam(short a) const &)(int b) = 0; |
| // which will yield `float (* const &)(int b)`. |
| // It appears that we'll need to parse the text manually looking for |
| // the closing paren to determine where type_rhs should begin. |
| if (SameFileRangesOverlapOpenInterval(type_lhs, name_range_)) { |
| type_rhs = clang::SourceRange(name_range_.getEnd(), type_lhs.getEnd()); |
| type_lhs = |
| clang::SourceRange(type_lhs.getBegin(), name_range_.getBegin()); |
| } |
| if (SameFileRangesOverlapOpenInterval(type_rhs, arg_list)) { |
| if (type_lhs == type_rhs) { |
| type_lhs = clang::SourceRange(type_lhs.getBegin(), arg_list.getBegin()); |
| } |
| type_rhs = clang::SourceRange(arg_list.getEnd(), type_rhs.getEnd()); |
| } |
| if (type_lhs != type_rhs) { |
| InsertAnnotation(type_rhs, Annotation{Annotation::Type}); |
| } |
| InsertAnnotation(type_lhs, Annotation{Annotation::Type}); |
| } |
| void VisitVarDecl(clang::VarDecl* decl) { |
| if (const auto* type_source_info = decl->getTypeSourceInfo()) { |
| if (!ShouldSkipDecl(decl, type_source_info->getType(), |
| type_source_info->getTypeLoc().getSourceRange())) { |
| auto type_loc = ExpandRangeBySingleToken( |
| cache_->source_manager(), cache_->lang_options(), |
| type_source_info->getTypeLoc().getSourceRange()); |
| InsertTypeAnnotation(type_loc, clang::SourceRange{}); |
| } |
| } |
| } |
| void VisitFieldDecl(clang::FieldDecl* decl) { |
| if (const auto* type_source_info = decl->getTypeSourceInfo()) { |
| if (!ShouldSkipDecl(decl, type_source_info->getType(), |
| type_source_info->getTypeLoc().getSourceRange())) { |
| auto type_loc = ExpandRangeBySingleToken( |
| cache_->source_manager(), cache_->lang_options(), |
| type_source_info->getTypeLoc().getSourceRange()); |
| InsertTypeAnnotation(type_loc, clang::SourceRange{}); |
| } |
| } |
| } |
| void VisitObjCPropertyDecl(clang::ObjCPropertyDecl* decl) { |
| if (const auto* type_source_info = decl->getTypeSourceInfo()) { |
| if (!ShouldSkipDecl(decl, type_source_info->getType(), |
| type_source_info->getTypeLoc().getSourceRange())) { |
| auto type_loc = ExpandRangeBySingleToken( |
| cache_->source_manager(), cache_->lang_options(), |
| type_source_info->getTypeLoc().getSourceRange()); |
| InsertTypeAnnotation(type_loc, clang::SourceRange{}); |
| } |
| } |
| } |
| void VisitFunctionDecl(clang::FunctionDecl* decl) { |
| clang::SourceRange arg_list; |
| if (const auto* type_info = decl->getTypeSourceInfo()) { |
| if (!ShouldSkipDecl(decl, type_info->getType(), |
| type_info->getTypeLoc().getSourceRange())) { |
| if (auto function_type = type_info->getTypeLoc() |
| .IgnoreParens() |
| .getAs<clang::FunctionTypeLoc>()) { |
| arg_list = ExpandRangeBySingleToken(cache_->source_manager(), |
| cache_->lang_options(), |
| function_type.getParensRange()); |
| InsertAnnotation(arg_list, Annotation{Annotation::ArgListWithParens}); |
| } |
| } |
| } |
| auto type_range = decl->getReturnTypeSourceRange(); |
| if (!type_range.isValid()) { |
| type_range = |
| GetReturnTypeSourceRangeForFunctionPointerReturningFunction(decl); |
| } |
| if (!ShouldSkipDecl(decl, decl->getReturnType(), type_range) && |
| type_range.isValid()) { |
| InsertTypeAnnotation( |
| ExpandRangeBySingleToken(cache_->source_manager(), |
| cache_->lang_options(), type_range), |
| arg_list); |
| } |
| } |
| |
| void VisitObjCMethodDecl(clang::ObjCMethodDecl* decl) { |
| // TODO(salguarneri) Do something sensible for selectors and arguments. |
| // Selectors are effectively the name of the method, but the selectors are |
| // interrupted in source code by parameters, so we don't have a single range |
| // for the method name or the method parameters. For example: |
| // -(void) myFunc:(int)size withTimeout:(int)time. The "name" should be |
| // myFunc:withTimeout and the arguments should be something like |
| // "(int)size, (int)time". |
| auto ret_type_range = ExpandRangeBySingleToken( |
| cache_->source_manager(), cache_->lang_options(), |
| decl->getReturnTypeSourceRange()); |
| if (ret_type_range.isValid()) { |
| InsertAnnotation(ret_type_range, Annotation{Annotation::Type}); |
| } else { |
| LOG(WARNING) << "Invalid return type range for " |
| << decl->getNameAsString(); |
| } |
| } |
| |
| void Annotate(const clang::NamedDecl* named_decl) { |
| Visit(const_cast<clang::NamedDecl*>(named_decl)); |
| CompleteMarkedSource(); |
| } |
| |
| private: |
| /// \brief Convert the annotations we've found to `MarkedSource`. |
| void CompleteMarkedSource() { |
| // We can get overlapping annotation ranges because of (for example) |
| // the bizarre concrete syntax for function pointers. |
| std::sort(annotations_.begin(), annotations_.end()); |
| NodeStack node_stack; |
| if (auto* ident_node = node_stack.ProcessAnnotations( |
| formatted_range_, annotations_, marked_source_)) { |
| ident_node_ = ident_node; |
| } |
| } |
| |
| /// \brief adds an annotation to the annotation list, transforming |
| /// offsets from original source to reformatted source. |
| void InsertAnnotation(const clang::SourceRange& original_range, |
| Annotation&& annotation) { |
| unsigned start_offset = original_range.getBegin().getRawEncoding() - |
| original_begin_.getRawEncoding(); |
| unsigned end_offset; |
| if (original_range.getBegin() == original_range.getEnd()) { |
| end_offset = start_offset; |
| } else { |
| end_offset = original_range.getEnd().getRawEncoding() - |
| original_begin_.getRawEncoding(); |
| } |
| if (replacements_ != nullptr) { |
| annotation.begin = replacements_->getShiftedCodePosition(start_offset); |
| annotation.end = replacements_->getShiftedCodePosition(end_offset); |
| } else { |
| annotation.begin = start_offset; |
| annotation.end = end_offset; |
| } |
| if (annotation.begin >= annotation.end || |
| annotation.end > formatted_range_.size()) { |
| // TODO(#1632): This is a symptom of #1632. This check is here to avoid |
| // clogging log output. |
| if (annotation.kind != Annotation::QualifiedName && |
| IsValidRange(cache_->source_manager(), original_range)) { |
| LOG(WARNING) |
| << "Invalid annotation range (" << annotation.kind << "): '" |
| << original_range.getBegin().printToString(cache_->source_manager()) |
| << "' to '" |
| << original_range.getEnd().printToString(cache_->source_manager()) |
| << "': became " << annotation.begin << " <= " << annotation.end |
| << " <= " << formatted_range_.size() |
| << " (text: " << formatted_range_ << ")"; |
| } |
| return; |
| } |
| annotations_.push_back(annotation); |
| } |
| |
| /// \brief determines if we should skip trying to record an annotation for |
| /// this decl. |
| /// |
| /// In Objective-C, the nullability attribute is troublesome because: |
| /// 1) The range we get for the Decl is backwards (goes from the e to the n in |
| /// nullable). |
| /// 2) The token is transformed to _Nullable by the time we analyze. nullable |
| /// is placed to the left of types, _Nullable is placed to the right of types. |
| bool ShouldSkipDecl(const clang::Decl* decl, const clang::QualType& qt, |
| const clang::SourceRange& sr) { |
| clang::Optional<clang::NullabilityKind> k = |
| qt->getNullability(decl->getASTContext()); |
| return k && sr.getBegin().getRawEncoding() > sr.getEnd().getRawEncoding(); |
| } |
| |
| MarkedSourceCache* cache_; |
| clang::tooling::Replacements* replacements_; |
| clang::SourceLocation original_begin_; |
| const std::string& formatted_range_; |
| MarkedSource* marked_source_; |
| const clang::SourceRange& name_range_; |
| MarkedSource* ident_node_ = nullptr; |
| std::vector<Annotation> annotations_; |
| }; |
| } // anonymous namespace |
| |
| bool MarkedSourceGenerator::WillGenerateMarkedSource() const { |
| // Be conservative in which kinds of marked source we'll generate. |
| // We can enable more AST node flavors as necessary. |
| if (decl_->isImplicit() || implicit_) { |
| return false; |
| } |
| return llvm::isa<clang::FunctionDecl>(decl_) || |
| llvm::isa<clang::VarDecl>(decl_) || |
| llvm::isa<clang::NamespaceDecl>(decl_) || |
| llvm::isa<clang::TagDecl>(decl_) || |
| llvm::isa<clang::TypedefNameDecl>(decl_) || |
| llvm::isa<clang::FieldDecl>(decl_) || |
| llvm::isa<clang::EnumConstantDecl>(decl_) || |
| llvm::isa<clang::ObjCMethodDecl>(decl_) || |
| llvm::isa<clang::ObjCContainerDecl>(decl_) || |
| llvm::isa<clang::TemplateTypeParmDecl>(decl_) || |
| llvm::isa<clang::NonTypeTemplateParmDecl>(decl_) || |
| llvm::isa<clang::TemplateTemplateParmDecl>(decl_) || |
| llvm::isa<clang::ObjCTypeParamDecl>(decl_) || |
| llvm::isa<clang::ObjCPropertyDecl>(decl_); |
| } |
| |
| std::string GetDeclName(const clang::LangOptions& lang_options, |
| const clang::NamedDecl* decl) { |
| auto name = decl->getDeclName(); |
| auto identifier_info = name.getAsIdentifierInfo(); |
| if (identifier_info && !identifier_info->getName().empty()) { |
| return identifier_info->getName(); |
| } else if (name.getCXXOverloadedOperator() != clang::OO_None) { |
| switch (name.getCXXOverloadedOperator()) { |
| #define OVERLOADED_OPERATOR(Name, Spelling, Token, Unary, Binary, MemberOnly) \ |
| case clang::OO_##Name: \ |
| return "operator " #Name; |
| #include "clang/Basic/OperatorKinds.def" |
| #undef OVERLOADED_OPERATOR |
| default: |
| break; |
| } |
| } else if (const auto* method_decl = |
| llvm::dyn_cast<clang::CXXMethodDecl>(decl)) { |
| if (llvm::isa<clang::CXXConstructorDecl>(method_decl)) { |
| return "(ctor)"; |
| } else if (llvm::isa<clang::CXXDestructorDecl>(method_decl)) { |
| return "(dtor)"; |
| } else if (const auto* conv_decl = |
| llvm::dyn_cast<clang::CXXConversionDecl>(method_decl)) { |
| auto to_type = conv_decl->getConversionType(); |
| if (!to_type.isNull()) { |
| std::string substring; |
| llvm::raw_string_ostream substream(substring); |
| substream << "operator "; |
| to_type.print(substream, clang::PrintingPolicy(lang_options)); |
| substream.flush(); |
| return substring; |
| } |
| } |
| } else if (isObjCSelector(name)) { |
| const auto sel = name.getObjCSelector(); |
| return sel.getAsString(); |
| } |
| return ""; |
| } |
| |
| void MarkedSourceGenerator::ReplaceMarkedSourceWithTemplateArgumentList( |
| MarkedSource* marked_source_node, |
| const clang::ClassTemplateSpecializationDecl* decl) { |
| auto* template_decl = decl->getSpecializedTemplate(); |
| auto* template_params = template_decl->getTemplateParameters(); |
| auto cached_default = cache_->first_default_template_argument()->find(decl); |
| const auto& template_args = decl->getTemplateArgs(); |
| unsigned noprint; |
| if (cached_default != cache_->first_default_template_argument()->end()) { |
| noprint = cached_default->second; |
| } else { |
| // Find a point N such that args[0..N-1] entirely predict args[N] and |
| // beyond. Start by guessing that N = first_default (the case where all |
| // default args are used). |
| unsigned first_default = template_params->getMinRequiredArguments(); |
| clang::TemplateArgumentListInfo list_prefix; |
| auto add_template_argument = [&](const clang::TemplateArgument& arg) { |
| switch (arg.getKind()) { |
| case clang::TemplateArgument::Null: |
| // This argument has not been deduced. |
| return false; |
| case clang::TemplateArgument::Type: |
| list_prefix.addArgument(clang::TemplateArgumentLoc( |
| arg, cache_->sema()->getASTContext().getTrivialTypeSourceInfo( |
| arg.getAsType()))); |
| return true; |
| case clang::TemplateArgument::Declaration: |
| case clang::TemplateArgument::NullPtr: |
| case clang::TemplateArgument::Integral: |
| case clang::TemplateArgument::Template: |
| case clang::TemplateArgument::TemplateExpansion: |
| case clang::TemplateArgument::Expression: |
| case clang::TemplateArgument::Pack: |
| // TODO(zarko): Remaining cases. |
| return false; |
| } |
| }; |
| for (unsigned n = 0; n < first_default; ++n) { |
| if (!add_template_argument(template_args.get(n))) { |
| // Abort if we can't complete a template_args list. |
| first_default = template_args.size(); |
| break; |
| } |
| } |
| llvm::SmallVector<clang::TemplateArgument, 4> out_arguments; |
| noprint = first_default; |
| for (; noprint < template_args.size(); ++noprint) { |
| bool was_ok = !cache_->sema()->CheckTemplateArgumentList( |
| template_decl, template_decl->getLocation(), list_prefix, false, |
| out_arguments); |
| if (was_ok) { |
| if (out_arguments.size() != template_args.size()) { |
| break; |
| } |
| unsigned arg_index = 0; |
| for (const auto& arg : out_arguments) { |
| // TODO(zarko): for certain kinds of declarations, source_arg may be |
| // a tyvar reference ('type-parameter-0-0'). Can we thread through |
| // the type context in those cases? |
| const auto& source_arg = template_args.get(arg_index); |
| if (arg.structurallyEquals(source_arg)) { |
| ++arg_index; |
| } else { |
| break; |
| } |
| } |
| if (arg_index == template_args.size()) { |
| break; |
| } |
| } |
| add_template_argument(template_args.get(noprint)); |
| } |
| (*cache_->first_default_template_argument())[decl] = noprint; |
| } |
| auto* typarams = marked_source_node->add_child(); |
| typarams->set_kind(MarkedSource::PARAMETER); |
| typarams->set_pre_text("<"); |
| typarams->set_post_child_text(", "); |
| typarams->set_post_text(">"); |
| typarams->set_default_children_count(template_args.size() - noprint); |
| auto policy = clang::PrintingPolicy(cache_->lang_options()); |
| for (const auto& print_arg : template_args.asArray()) { |
| auto* next_arg = typarams->add_child(); |
| typarams->set_kind(MarkedSource::BOX); |
| // TODO(zarko): Call ReplaceMarkedSourceWithQualifiedName recursively |
| // instead of using the pretty printer? If we do this, we'll need to update |
| // the type context. |
| // TODO(zarko): Pack expansions; |
| // see TemplateSpecializationType::PrintTemplateArgumentList |
| std::string pre_text; |
| { |
| llvm::raw_string_ostream stream(pre_text); |
| print_arg.print(policy, stream); |
| } |
| *next_arg->mutable_pre_text() = pre_text; |
| } |
| } |
| |
| bool MarkedSourceGenerator::ReplaceMarkedSourceWithQualifiedName( |
| MarkedSource* node) { |
| // We could also consider populating the context dynamically at serving |
| // or denormalization time, but doing this requires unbounded recursive |
| // queries, so it's probably not worth it. |
| // See also TypePrinter::AppendScope, NestedNameSpecifier::print, |
| // NamedDecl::printQualifiedName (from which this code is derived). |
| |
| // Collect contexts. |
| const auto* decl_context = decl_->getDeclContext(); |
| llvm::SmallVector<const clang::DeclContext*, 8> contexts; |
| while (decl_context && llvm::isa<clang::NamedDecl>(decl_context)) { |
| contexts.push_back(decl_context); |
| decl_context = decl_context->getParent(); |
| } |
| |
| MarkedSource* self = node; |
| if (!contexts.empty()) { |
| // Avoid creating an unnecessary BOX if there are no context nodes. |
| auto* parents = node->add_child(); |
| self = node->add_child(); |
| parents->set_kind(MarkedSource::CONTEXT); |
| parents->set_add_final_list_token(true); |
| parents->set_post_child_text("::"); |
| auto policy = clang::PrintingPolicy(cache_->lang_options()); |
| for (const auto* decl_context : reverse(contexts)) { |
| auto* parent = parents->add_child(); |
| if (const auto* spec = |
| llvm::dyn_cast<clang::ClassTemplateSpecializationDecl>( |
| decl_context)) { |
| parent->set_kind(MarkedSource::BOX); |
| auto* class_name = parent->add_child(); |
| class_name->set_kind(MarkedSource::IDENTIFIER); |
| std::string pre_text; |
| { |
| llvm::raw_string_ostream stream(pre_text); |
| stream << spec->getName(); |
| } |
| *class_name->mutable_pre_text() = pre_text; |
| ReplaceMarkedSourceWithTemplateArgumentList(parent->add_child(), spec); |
| } else { |
| parent->set_kind(MarkedSource::IDENTIFIER); |
| std::string pre_text; |
| { |
| llvm::raw_string_ostream stream(pre_text); |
| if (const auto* namespace_decl = |
| llvm::dyn_cast<clang::NamespaceDecl>(decl_context)) { |
| if (namespace_decl->isAnonymousNamespace()) { |
| stream << (policy.MSVCFormatting ? "`anonymous namespace\'" |
| : "(anonymous namespace)"); |
| } else { |
| stream << *namespace_decl; |
| } |
| } else if (const auto* record_decl = |
| llvm::dyn_cast<clang::RecordDecl>(decl_context)) { |
| if (!record_decl->getIdentifier()) |
| stream << "(anonymous " << record_decl->getKindName() << ')'; |
| else |
| stream << *record_decl; |
| } else if (const auto* function_decl = |
| llvm::dyn_cast<clang::FunctionDecl>(decl_context)) { |
| stream << *function_decl; |
| } else if (const auto* enum_decl = |
| llvm::dyn_cast<clang::EnumDecl>(decl_context)) { |
| stream << *enum_decl; |
| } else if (const auto* cat_decl = |
| llvm::dyn_cast<clang::ObjCCategoryDecl>( |
| decl_context)) { |
| // Print categories methods as |
| // 'InterfaceName(CategoryName)::Method'. |
| if (const auto* i = cat_decl->getClassInterface()) { |
| stream << i->getName(); |
| } |
| stream << "(" << cat_decl->getName() << ")"; |
| } else if (const auto* cat_impl = |
| llvm::dyn_cast<clang::ObjCCategoryImplDecl>( |
| decl_context)) { |
| // Print categories methods as |
| // 'InterfaceName(CategoryName)::Method'. |
| if (const auto* decl = cat_impl->getCategoryDecl()) { |
| if (const auto* i = decl->getClassInterface()) { |
| stream << i->getName(); |
| } |
| } |
| stream << "(" << cat_impl->getName() << ")"; |
| } else { |
| stream << *llvm::cast<clang::NamedDecl>(decl_context); |
| } |
| } |
| *parent->mutable_pre_text() = pre_text; |
| } |
| } |
| } |
| self->set_kind(MarkedSource::IDENTIFIER); |
| self->set_pre_text(GetDeclName(cache_->lang_options(), decl_)); |
| return true; |
| } |
| |
| absl::optional<MarkedSource> |
| MarkedSourceGenerator::GenerateMarkedSourceUsingSource( |
| const GraphObserver::NodeId& decl_id) { |
| auto start_loc = decl_->getSourceRange().getBegin(); |
| if (start_loc.isMacroID()) { |
| start_loc = cache_->source_manager().getExpansionLoc(start_loc); |
| } |
| auto end_loc = end_loc_.isMacroID() |
| ? cache_->source_manager().getExpansionLoc(end_loc_) |
| : end_loc_; |
| auto range = GetTextRange(cache_->source_manager(), |
| clang::SourceRange(start_loc, end_loc)); |
| if (range.empty()) { |
| if (VLOG_IS_ON(1)) { |
| VLOG(1) << "GetTextRange failed for " << decl_->getDeclKindName() << " " |
| << decl_->getQualifiedNameAsString() << "\n at " |
| << start_loc.printToString(cache_->source_manager()) << "\n to " |
| << end_loc.printToString(cache_->source_manager()) |
| << "\n originally " |
| << decl_->getSourceRange().getBegin().printToString( |
| cache_->source_manager()) |
| << "\n to " |
| << end_loc_.printToString(cache_->source_manager()); |
| } |
| return absl::nullopt; |
| } |
| MarkedSource out_sig; |
| if (FLAGS_reformat_marked_source) { |
| clang::tooling::Replacements replacements; |
| bool incomplete = false; |
| // Weirdly, Clang tooling complains if we don't make a copy of `range` here. |
| auto formatted_range = Reformat(cache_->lang_options(), range.str(), |
| &replacements, &incomplete); |
| if (incomplete) { |
| LOG(WARNING) << "Incomplete reformatting for " << decl_id.getRawIdentity() |
| << " (" << decl_->getQualifiedNameAsString() << ")"; |
| return absl::nullopt; |
| } |
| DeclAnnotator annotator(cache_, &replacements, start_loc, formatted_range, |
| &out_sig, name_range_); |
| annotator.Annotate(decl_); |
| ReplaceMarkedSourceWithQualifiedName(annotator.ident_node()); |
| } else { |
| auto range_string = range.str(); |
| DeclAnnotator annotator(cache_, nullptr, start_loc, range_string, &out_sig, |
| name_range_); |
| annotator.Annotate(decl_); |
| ReplaceMarkedSourceWithQualifiedName(annotator.ident_node()); |
| } |
| return out_sig; |
| } |
| |
| MarkedSource MarkedSourceGenerator::GenerateMarkedSourceForFunction( |
| const clang::FunctionDecl* func) { |
| MarkedSource out; |
| ReplaceMarkedSourceWithQualifiedName(out.add_child()); |
| auto* child = out.add_child(); |
| child->set_kind(MarkedSource::PARAMETER_LOOKUP_BY_PARAM); |
| child->set_pre_text("("); |
| child->set_post_child_text(", "); |
| child->set_post_text(")"); |
| return out; |
| } |
| |
| MarkedSource MarkedSourceGenerator::GenerateMarkedSourceForNamedDecl() { |
| MarkedSource out; |
| ReplaceMarkedSourceWithQualifiedName(&out); |
| return out; |
| } |
| |
| absl::optional<MarkedSource> MarkedSourceGenerator::GenerateMarkedSource( |
| const GraphObserver::NodeId& decl_id) { |
| // MarkedSource generation is expensive. If we're not going to write out the |
| // marked source later on, don't spend time on it. |
| // TODO(zarko): Introduce a similar check for documentation. |
| if (!WillGenerateMarkedSource()) { |
| return absl::nullopt; |
| } |
| if (llvm::isa<clang::VarDecl>(decl_) || llvm::isa<clang::FieldDecl>(decl_)) { |
| return GenerateMarkedSourceUsingSource(decl_id); |
| } else if (const auto* func = llvm::dyn_cast<clang::FunctionDecl>(decl_)) { |
| if (FLAGS_pretty_print_function_prototypes) { |
| return GenerateMarkedSourceForFunction(func); |
| } else { |
| return GenerateMarkedSourceUsingSource(decl_id); |
| } |
| } else if (llvm::isa<clang::ObjCPropertyDecl>(decl_)) { |
| return GenerateMarkedSourceUsingSource(decl_id); |
| } else if (llvm::isa<clang::ObjCMethodDecl>(decl_)) { |
| return GenerateMarkedSourceUsingSource(decl_id); |
| } else if (llvm::isa<clang::TemplateTypeParmDecl>(decl_) || |
| llvm::isa<clang::NonTypeTemplateParmDecl>(decl_) || |
| llvm::isa<clang::TemplateTemplateParmDecl>(decl_) || |
| llvm::isa<clang::ObjCTypeParamDecl>(decl_)) { |
| MarkedSource self; |
| self.set_kind(MarkedSource::IDENTIFIER); |
| self.set_pre_text(GetDeclName(cache_->lang_options(), decl_)); |
| return self; |
| } |
| return GenerateMarkedSourceForNamedDecl(); |
| } |
| } // namespace kythe |