/// this ASTContext object.
LangOptions LangOpts;
+ /// \brief Whether we have already loaded comment source ranges from an
+ /// external source.
+ bool LoadedExternalComments;
+
/// MallocAlloc/BumpAlloc - The allocator objects used to create AST objects.
bool FreeMemory;
llvm::MallocAllocator MallocAlloc;
llvm::BumpPtrAllocator BumpAlloc;
+
+ /// \brief Mapping from declarations to their comments, once we have
+ /// already looked up the comment associated with a given declaration.
+ llvm::DenseMap<const Decl *, std::string> DeclComments;
+
public:
TargetInfo &Target;
IdentifierTable &Idents;
llvm::OwningPtr<ExternalASTSource> ExternalSource;
clang::PrintingPolicy PrintingPolicy;
+ /// \brief Source ranges for all of the comments in the source file,
+ /// sorted in order of appearance in the translation unit.
+ std::vector<SourceRange> Comments;
+
SourceManager& getSourceManager() { return SourceMgr; }
const SourceManager& getSourceManager() const { return SourceMgr; }
void *Allocate(unsigned Size, unsigned Align = 8) {
TranslationUnitDecl *getTranslationUnitDecl() const { return TUDecl; }
-
+ const char *getCommentForDecl(const Decl *D);
+
// Builtin Types.
QualType VoidTy;
QualType BoolTy;
#include "clang/AST/Type.h"
#include "llvm/ADT/SmallVector.h"
#include <cassert>
+#include <vector>
namespace clang {
class ASTConsumer;
virtual ~ExternalASTSource();
+ /// \brief Reads the source ranges that correspond to comments from
+ /// an external AST source.
+ ///
+ /// \param Comments the contents of this vector will be
+ /// replaced with the sorted set of source ranges corresponding to
+ /// comments in the source code.
+ virtual void ReadComments(std::vector<SourceRange> &Comments) = 0;
+
/// \brief Resolve a type ID into a type, potentially building a new
/// type.
virtual QualType GetType(uint32_t ID) = 0;
/// \brief Record code for the original file that was used to
/// generate the precompiled header.
- ORIGINAL_FILE_NAME = 20
+ ORIGINAL_FILE_NAME = 20,
+
+ /// \brief Record code for the sorted array of source ranges where
+ /// comments were encountered in the source code.
+ COMMENT_RANGES = 21
};
/// \brief Record types used within a source manager block.
/// been loaded.
llvm::SmallVector<Selector, 16> SelectorsLoaded;
+ /// \brief A sorted array of source ranges containing comments.
+ SourceRange *Comments;
+
+ /// \brief The number of source ranges in the Comments array.
+ unsigned NumComments;
+
/// \brief The set of external definitions stored in the the PCH
/// file.
llvm::SmallVector<uint64_t, 16> ExternalDefinitions;
/// build prior to including the precompiled header.
const std::string &getSuggestedPredefines() { return SuggestedPredefines; }
+ /// \brief Reads the source ranges that correspond to comments from
+ /// an external AST source.
+ ///
+ /// \param Comments the contents of this vector will be
+ /// replaced with the sorted set of source ranges corresponding to
+ /// comments in the source code.
+ virtual void ReadComments(std::vector<SourceRange> &Comments);
+
/// \brief Resolve a type ID into a type, potentially building a new
/// type.
virtual QualType GetType(pch::TypeID ID);
void WriteSourceManagerBlock(SourceManager &SourceMgr,
const Preprocessor &PP);
void WritePreprocessor(const Preprocessor &PP);
+ void WriteComments(ASTContext &Context);
void WriteType(const Type *T);
void WriteTypesBlock(ASTContext &Context);
uint64_t WriteDeclContextLexicalBlock(ASTContext &Context, DeclContext *DC);
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/OwningPtr.h"
#include "llvm/Support/Allocator.h"
+#include <vector>
namespace clang {
class HeaderSearch;
class PragmaNamespace;
class PragmaHandler;
+class CommentHandler;
class ScratchBuffer;
class TargetInfo;
class PPCallbacks;
/// with this preprocessor.
PragmaNamespace *PragmaHandlers;
+ /// \brief Tracks all of the comment handlers that the client registered
+ /// with this preprocessor.
+ std::vector<CommentHandler *> CommentHandlers;
+
/// CurLexer - This is the current top of the stack that we're lexing from if
/// not expanding a macro and we are lexing directly from source code.
/// Only one of CurLexer, CurPTHLexer, or CurTokenLexer will be non-null.
/// to remove a handler that has not been registered.
void RemovePragmaHandler(const char *Namespace, PragmaHandler *Handler);
+ /// \brief Add the specified comment handler to the preprocessor.
+ void AddCommentHandler(CommentHandler *Handler);
+
+ /// \brief Remove the specified comment handler.
+ ///
+ /// It is an error to remove a handler that has not been registered.
+ void RemoveCommentHandler(CommentHandler *Handler);
+
/// EnterMainSourceFile - Enter the specified FileID as the main source file,
/// which implicitly adds the builtin defines etc.
void EnterMainSourceFile();
void HandlePragmaSystemHeader(Token &SysHeaderTok);
void HandlePragmaDependency(Token &DependencyTok);
void HandlePragmaComment(Token &CommentTok);
+ void HandleComment(SourceRange Comment);
};
/// PreprocessorFactory - A generic factory interface for lazily creating
virtual Preprocessor* CreatePreprocessor() = 0;
};
+/// \brief Abstract base class that describes a handler that will receive
+/// source ranges for each of the comments encountered in the source file.
+class CommentHandler {
+public:
+ virtual ~CommentHandler();
+
+ virtual void HandleComment(Preprocessor &PP, SourceRange Comment) = 0;
+};
+
} // end namespace clang
#endif
/// an empty string if not. This is used for pretty crash reporting.
virtual std::string getDeclName(DeclPtrTy D) { return ""; }
+ /// \brief Invoked for each comment in the source code, providing the source
+ /// range that contains the comment.
+ virtual void ActOnComment(SourceRange Comment) { }
+
//===--------------------------------------------------------------------===//
// Declaration Tracking Callbacks.
//===--------------------------------------------------------------------===//
llvm::OwningPtr<PragmaHandler> PackHandler;
llvm::OwningPtr<PragmaHandler> UnusedHandler;
llvm::OwningPtr<PragmaHandler> WeakHandler;
-
+ llvm::OwningPtr<clang::CommentHandler> CommentHandler;
+
/// Whether the '>' token acts as an operator or not. This will be
/// true except when we are parsing an expression within a C++
/// template argument list, where the '>' closes the template
bool FreeMem, unsigned size_reserve) :
GlobalNestedNameSpecifier(0), CFConstantStringTypeDecl(0),
ObjCFastEnumerationStateTypeDecl(0), SourceMgr(SM), LangOpts(LOpts),
- FreeMemory(FreeMem), Target(t), Idents(idents), Selectors(sels),
+ LoadedExternalComments(false), FreeMemory(FreeMem), Target(t),
+ Idents(idents), Selectors(sels),
BuiltinInfo(builtins), ExternalSource(0), PrintingPolicy(LOpts) {
if (size_reserve > 0) Types.reserve(size_reserve);
InitBuiltinTypes();
InitBuiltinType(NullPtrTy, BuiltinType::NullPtr);
}
+namespace {
+ class BeforeInTranslationUnit
+ : std::binary_function<SourceRange, SourceRange, bool> {
+ SourceManager *SourceMgr;
+
+ public:
+ explicit BeforeInTranslationUnit(SourceManager *SM) : SourceMgr(SM) { }
+
+ bool operator()(SourceRange X, SourceRange Y) {
+ return SourceMgr->isBeforeInTranslationUnit(X.getBegin(), Y.getBegin());
+ }
+ };
+}
+
+/// \brief Determine whether the given comment is a Doxygen-style comment.
+///
+/// \param Start the start of the comment text.
+///
+/// \param End the end of the comment text.
+///
+/// \param Member whether we want to check whether this is a member comment
+/// (which requires a < after the Doxygen-comment delimiter). Otherwise,
+/// we only return true when we find a non-member comment.
+static bool
+isDoxygenComment(SourceManager &SourceMgr, SourceRange Comment,
+ bool Member = false) {
+ const char *BufferStart
+ = SourceMgr.getBufferData(SourceMgr.getFileID(Comment.getBegin())).first;
+ const char *Start = BufferStart + SourceMgr.getFileOffset(Comment.getBegin());
+ const char* End = BufferStart + SourceMgr.getFileOffset(Comment.getEnd());
+
+ if (End - Start < 4)
+ return false;
+
+ assert(Start[0] == '/' && "Not a comment?");
+ if (Start[1] == '*' && !(Start[2] == '!' || Start[2] == '*'))
+ return false;
+ if (Start[1] == '/' && !(Start[2] == '!' || Start[2] == '/'))
+ return false;
+
+ return (Start[3] == '<') == Member;
+}
+
+/// \brief Retrieve the comment associated with the given declaration, if
+/// it has one.
+const char *ASTContext::getCommentForDecl(const Decl *D) {
+ if (!D)
+ return 0;
+
+ // Check whether we have cached a comment string for this declaration
+ // already.
+ llvm::DenseMap<const Decl *, std::string>::iterator Pos
+ = DeclComments.find(D);
+ if (Pos != DeclComments.end())
+ return Pos->second.c_str();
+
+ // If we have an external AST source and have not yet loaded comments from
+ // that source, do so now.
+ if (ExternalSource && !LoadedExternalComments) {
+ std::vector<SourceRange> LoadedComments;
+ ExternalSource->ReadComments(LoadedComments);
+
+ if (!LoadedComments.empty())
+ Comments.insert(Comments.begin(), LoadedComments.begin(),
+ LoadedComments.end());
+
+ LoadedExternalComments = true;
+ }
+
+ // If there are no comments anywhere, we won't find anything.
+ if (Comments.empty())
+ return 0;
+
+ // If the declaration doesn't map directly to a location in a file, we
+ // can't find the comment.
+ SourceLocation DeclStartLoc = D->getLocStart();
+ if (DeclStartLoc.isInvalid() || !DeclStartLoc.isFileID())
+ return 0;
+
+ // Find the comment that occurs just before this declaration.
+ std::vector<SourceRange>::iterator LastComment
+ = std::lower_bound(Comments.begin(), Comments.end(),
+ SourceRange(DeclStartLoc),
+ BeforeInTranslationUnit(&SourceMgr));
+
+ // Decompose the location for the start of the declaration and find the
+ // beginning of the file buffer.
+ std::pair<FileID, unsigned> DeclStartDecomp
+ = SourceMgr.getDecomposedLoc(DeclStartLoc);
+ const char *FileBufferStart
+ = SourceMgr.getBufferData(DeclStartDecomp.first).first;
+
+ // First check whether we have a comment for a member.
+ if (LastComment != Comments.end() &&
+ !isa<TagDecl>(D) && !isa<NamespaceDecl>(D) &&
+ isDoxygenComment(SourceMgr, *LastComment, true)) {
+ std::pair<FileID, unsigned> LastCommentEndDecomp
+ = SourceMgr.getDecomposedLoc(LastComment->getEnd());
+ if (DeclStartDecomp.first == LastCommentEndDecomp.first &&
+ SourceMgr.getLineNumber(DeclStartDecomp.first, DeclStartDecomp.second)
+ == SourceMgr.getLineNumber(LastCommentEndDecomp.first,
+ LastCommentEndDecomp.second)) {
+ // The Doxygen member comment comes after the declaration starts and
+ // is on the same line and in the same file as the declaration. This
+ // is the comment we want.
+ std::string &Result = DeclComments[D];
+ Result.append(FileBufferStart +
+ SourceMgr.getFileOffset(LastComment->getBegin()),
+ FileBufferStart + LastCommentEndDecomp.second + 1);
+ return Result.c_str();
+ }
+ }
+
+ if (LastComment == Comments.begin())
+ return 0;
+ --LastComment;
+
+ // Decompose the end of the comment.
+ std::pair<FileID, unsigned> LastCommentEndDecomp
+ = SourceMgr.getDecomposedLoc(LastComment->getEnd());
+
+ // If the comment and the declaration aren't in the same file, then they
+ // aren't related.
+ if (DeclStartDecomp.first != LastCommentEndDecomp.first)
+ return 0;
+
+ // Check that we actually have a Doxygen comment.
+ if (!isDoxygenComment(SourceMgr, *LastComment))
+ return 0;
+
+ // Compute the starting line for the declaration and for the end of the
+ // comment (this is expensive).
+ unsigned DeclStartLine
+ = SourceMgr.getLineNumber(DeclStartDecomp.first, DeclStartDecomp.second);
+ unsigned CommentEndLine
+ = SourceMgr.getLineNumber(LastCommentEndDecomp.first,
+ LastCommentEndDecomp.second);
+
+ // If the comment does not end on the line prior to the declaration, then
+ // the comment is not associated with the declaration at all.
+ if (CommentEndLine + 1 != DeclStartLine)
+ return 0;
+
+ // We have a comment, but there may be more comments on the previous lines.
+ // Keep looking so long as the comments are still Doxygen comments and are
+ // still adjacent.
+ unsigned ExpectedLine
+ = SourceMgr.getSpellingLineNumber(LastComment->getBegin()) - 1;
+ std::vector<SourceRange>::iterator FirstComment = LastComment;
+ while (FirstComment != Comments.begin()) {
+ // Look at the previous comment
+ --FirstComment;
+ std::pair<FileID, unsigned> Decomp
+ = SourceMgr.getDecomposedLoc(FirstComment->getEnd());
+
+ // If this previous comment is in a different file, we're done.
+ if (Decomp.first != DeclStartDecomp.first) {
+ ++FirstComment;
+ break;
+ }
+
+ // If this comment is not a Doxygen comment, we're done.
+ if (!isDoxygenComment(SourceMgr, *FirstComment)) {
+ ++FirstComment;
+ break;
+ }
+
+ // If the line number is not what we expected, we're done.
+ unsigned Line = SourceMgr.getLineNumber(Decomp.first, Decomp.second);
+ if (Line != ExpectedLine) {
+ ++FirstComment;
+ break;
+ }
+
+ // Set the next expected line number.
+ ExpectedLine
+ = SourceMgr.getSpellingLineNumber(FirstComment->getBegin()) - 1;
+ }
+
+ // The iterator range [FirstComment, LastComment] contains all of the
+ // BCPL comments that, together, are associated with this declaration.
+ // Form a single comment block string for this declaration that concatenates
+ // all of these comments.
+ std::string &Result = DeclComments[D];
+ while (FirstComment != LastComment) {
+ std::pair<FileID, unsigned> DecompStart
+ = SourceMgr.getDecomposedLoc(FirstComment->getBegin());
+ std::pair<FileID, unsigned> DecompEnd
+ = SourceMgr.getDecomposedLoc(FirstComment->getEnd());
+ Result.append(FileBufferStart + DecompStart.second,
+ FileBufferStart + DecompEnd.second + 1);
+ ++FirstComment;
+ }
+
+ // Append the last comment line.
+ Result.append(FileBufferStart +
+ SourceMgr.getFileOffset(LastComment->getBegin()),
+ FileBufferStart + LastCommentEndDecomp.second + 1);
+ return Result.c_str();
+}
+
//===----------------------------------------------------------------------===//
// Type Sizing and Analysis
//===----------------------------------------------------------------------===//
IdentifierOffsets(0),
MethodPoolLookupTable(0), MethodPoolLookupTableData(0),
TotalSelectorsInMethodPool(0), SelectorOffsets(0),
- TotalNumSelectors(0), NumStatHits(0), NumStatMisses(0),
+ TotalNumSelectors(0), Comments(0), NumComments(0),
+ NumStatHits(0), NumStatMisses(0),
NumSLocEntriesRead(0), NumStatementsRead(0),
NumMacrosRead(0), NumMethodPoolSelectorsRead(0), NumMethodPoolMisses(0),
NumLexicalDeclContextsRead(0), NumVisibleDeclContextsRead(0) { }
case pch::ORIGINAL_FILE_NAME:
OriginalFileName.assign(BlobStart, BlobLen);
break;
+
+ case pch::COMMENT_RANGES:
+ Comments = (SourceRange *)BlobStart;
+ NumComments = BlobLen / sizeof(SourceRange);
+ break;
}
}
Error("premature end of bitstream in PCH file");
return false;
}
+void PCHReader::ReadComments(std::vector<SourceRange> &Comments) {
+ Comments.resize(NumComments);
+ std::copy(this->Comments, this->Comments + NumComments,
+ Comments.begin());
+}
+
/// \brief Read and return the type at the given offset.
///
/// This routine actually reads the record corresponding to the type
RECORD(STAT_CACHE);
RECORD(EXT_VECTOR_DECLS);
RECORD(OBJC_CATEGORY_IMPLEMENTATIONS);
-
+ RECORD(COMMENT_RANGES);
+
// SourceManager Block.
BLOCK(SOURCE_MANAGER_BLOCK);
RECORD(SM_SLOC_FILE_ENTRY);
Stream.ExitBlock();
}
+void PCHWriter::WriteComments(ASTContext &Context) {
+ using namespace llvm;
+
+ if (Context.Comments.empty())
+ return;
+
+ BitCodeAbbrev *CommentAbbrev = new BitCodeAbbrev();
+ CommentAbbrev->Add(BitCodeAbbrevOp(pch::COMMENT_RANGES));
+ CommentAbbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Blob));
+ unsigned CommentCode = Stream.EmitAbbrev(CommentAbbrev);
+
+ RecordData Record;
+ Record.push_back(pch::COMMENT_RANGES);
+ Stream.EmitRecordWithBlob(CommentCode, Record,
+ (const char*)&Context.Comments[0],
+ Context.Comments.size() * sizeof(SourceRange));
+}
+
//===----------------------------------------------------------------------===//
// Type Serialization
//===----------------------------------------------------------------------===//
WriteStatCache(*StatCalls);
WriteSourceManagerBlock(Context.getSourceManager(), PP);
WritePreprocessor(PP);
-
+ WriteComments(Context);
+
// Keep writing types and declarations until all types and
// declarations have been written.
do {
} while (C != '\n' && C != '\r');
// Found but did not consume the newline.
-
+ if (PP)
+ PP->HandleComment(SourceRange(getSourceLocation(BufferPtr),
+ getSourceLocation(CurPtr)));
+
// If we are returning comments as tokens, return this comment as a token.
if (inKeepCommentMode())
return SaveBCPLComment(Result, CurPtr);
C = *CurPtr++;
}
+ if (PP)
+ PP->HandleComment(SourceRange(getSourceLocation(BufferPtr),
+ getSourceLocation(CurPtr)));
+
// If we are returning comments as tokens, return this comment as a token.
if (inKeepCommentMode()) {
FormTokenWithChars(Result, CurPtr, tok::comment);
#include "clang/Lex/LexDiagnostic.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceManager.h"
+#include <algorithm>
using namespace clang;
// Out-of-line destructor to provide a home for the class.
if (II.isExtensionToken() && !DisableMacroExpansion)
Diag(Identifier, diag::ext_token_used);
}
+
+void Preprocessor::AddCommentHandler(CommentHandler *Handler) {
+ assert(Handler && "NULL comment handler");
+ assert(std::find(CommentHandlers.begin(), CommentHandlers.end(), Handler) ==
+ CommentHandlers.end() && "Comment handler already registered");
+ CommentHandlers.push_back(Handler);
+}
+
+void Preprocessor::RemoveCommentHandler(CommentHandler *Handler) {
+ std::vector<CommentHandler *>::iterator Pos
+ = std::find(CommentHandlers.begin(), CommentHandlers.end(), Handler);
+ assert(Pos != CommentHandlers.end() && "Comment handler not registered");
+ CommentHandlers.erase(Pos);
+}
+
+void Preprocessor::HandleComment(SourceRange Comment) {
+ for (std::vector<CommentHandler *>::iterator H = CommentHandlers.begin(),
+ HEnd = CommentHandlers.end();
+ H != HEnd; ++H)
+ (*H)->HandleComment(*this, Comment);
+}
+
+CommentHandler::~CommentHandler() { }
#include "ParsePragma.h"
using namespace clang;
+/// \brief A comment handler that passes comments found by the preprocessor
+/// to the parser action.
+class ActionCommentHandler : public CommentHandler {
+ Action &Actions;
+
+public:
+ explicit ActionCommentHandler(Action &Actions) : Actions(Actions) { }
+
+ virtual void HandleComment(Preprocessor &PP, SourceRange Comment) {
+ Actions.ActOnComment(Comment);
+ }
+};
+
Parser::Parser(Preprocessor &pp, Action &actions)
: CrashInfo(*this), PP(pp), Actions(actions), Diags(PP.getDiagnostics()),
GreaterThanIsOperator(true) {
WeakHandler.reset(new
PragmaWeakHandler(&PP.getIdentifierTable().get("weak"), actions));
PP.AddPragmaHandler(0, WeakHandler.get());
+
+ CommentHandler.reset(new ActionCommentHandler(actions));
+ PP.AddCommentHandler(CommentHandler.get());
}
/// If a crash happens while the parser is active, print out a line indicating
UnusedHandler.reset();
PP.RemovePragmaHandler(0, WeakHandler.get());
WeakHandler.reset();
+ PP.RemoveCommentHandler(CommentHandler.get());
}
/// Initialize - Warm up the parser.
= SemaRef.ActiveTemplateInstantiations.back();
}
}
+
+void Sema::ActOnComment(SourceRange Comment) {
+ Context.Comments.push_back(Comment);
+}
return CurBlock ? CurBlock->SwitchStack : FunctionSwitchStack;
}
+ virtual void ActOnComment(SourceRange Comment);
+
//===--------------------------------------------------------------------===//
// Type Analysis / Processing: SemaType.cpp.
//
"Driver"
"FixIt"
"Frontend"
+ "Index"
"Lexer"
"Misc"
"PCH"
--clang-cc=${LLVM_TOOLS_PATH}/${CMAKE_CFG_INTDIR}/clang-cc
${all_testdirs}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
- DEPENDS clang clang-cc
+ DEPENDS clang clang-cc index-test
COMMENT "Running Clang regression tests")
endif()
--- /dev/null
+// RUN: clang-cc -emit-pch -o %t.ast %s &&
+// RUN: index-test %t.ast -point-at %s:22:6 | grep "starts here" &&
+// RUN: index-test %t.ast -point-at %s:22:6 | grep "block comment" &&
+// RUN: index-test %t.ast -point-at %s:28:6 | grep "BCPL" &&
+// RUN: index-test %t.ast -point-at %s:28:6 | grep "But" &&
+// RUN: index-test %t.ast -point-at %s:28:6 | grep "NOT" | count 0 &&
+// RUN: index-test %t.ast -point-at %s:30:6 | grep "member"
+
+
+
+
+
+
+//! It all starts here.
+/*! It's a little odd to continue line this,
+ *
+ * but we need more multi-line comments. */
+/// This comment comes before my other comments
+/** This is a block comment that is associated with the function f. It
+ * runs for three lines.
+ */
+void f(int, int);
+
+// NOT IN THE COMMENT
+/// This is a BCPL comment that is associated with the function g.
+/// It has only two lines.
+/** But there are other blocks that are part of the comment, too. */
+void g(int);
+
+void h(int); ///< This is a member comment.
\ No newline at end of file
OS << ND->getNameAsString();
OS << "\n";
+ if (const char *Comment = AST->getASTContext().getCommentForDecl(Point.D))
+ OS << "Comment associated with this declaration:\n" << Comment << "\n";
+
if (Point.Node) {
OS << "Statement node at point: " << Point.Node->getStmtClassName()
<< " ";