Index: include/clang-c/Index.h =================================================================== --- include/clang-c/Index.h +++ include/clang-c/Index.h @@ -568,6 +568,36 @@ * \brief Retrieve the file, line, column, and offset represented by * the given source location. * + * If the location refers into a macro instantiation, return where the + * location was originally spelled in the source file (i.e. where the token + * text is located). clang_getSpellingLocation() is supposed to do this + * but doesn't, hence this function. + * + * \param location the location within a source file that will be decomposed + * into its parts. + * + * \param file [out] if non-NULL, will be set to the file to which the given + * source location points. + * + * \param line [out] if non-NULL, will be set to the line to which the given + * source location points. + * + * \param column [out] if non-NULL, will be set to the column to which the given + * source location points. + * + * \param offset [out] if non-NULL, will be set to the offset into the + * buffer to which the given source location points. + */ +CINDEX_LINKAGE void clang_getRealSpellingLocation(CXSourceLocation location, + CXFile *file, + unsigned *line, + unsigned *column, + unsigned *offset); + +/** + * \brief Retrieve the file, line, column, and offset represented by + * the given source location. + * * If the location refers into a macro expansion, return where the macro was * expanded or where the macro argument was written, if the location points at * a macro argument. Index: tools/libclang/CXSourceLocation.cpp =================================================================== --- tools/libclang/CXSourceLocation.cpp +++ tools/libclang/CXSourceLocation.cpp @@ -346,6 +346,42 @@ *offset = FileOffset; } +void clang_getRealSpellingLocation(CXSourceLocation location, + CXFile *file, + unsigned *line, + unsigned *column, + unsigned *offset) { + + if (!isASTUnitSourceLocation(location)) { + CXLoadedDiagnostic::decodeLocation(location, file, line, column, offset); + return; + } + + SourceLocation Loc = SourceLocation::getFromRawEncoding(location.int_data); + + if (!location.ptr_data[0] || Loc.isInvalid()) + return createNullLocation(file, line, column, offset); + + const SourceManager &SM = + *static_cast(location.ptr_data[0]); + SourceLocation SpellLoc = SM.getSpellingLoc(Loc); + std::pair LocInfo = SM.getDecomposedLoc(SpellLoc); + FileID FID = LocInfo.first; + unsigned FileOffset = LocInfo.second; + + if (FID.isInvalid()) + return createNullLocation(file, line, column, offset); + + if (file) + *file = const_cast(SM.getFileEntryForID(FID)); + if (line) + *line = SM.getLineNumber(FID, FileOffset); + if (column) + *column = SM.getColumnNumber(FID, FileOffset); + if (offset) + *offset = FileOffset; +} + void clang_getFileLocation(CXSourceLocation location, CXFile *file, unsigned *line, Index: unittests/libclang/LibclangTest.cpp =================================================================== --- unittests/libclang/LibclangTest.cpp +++ unittests/libclang/LibclangTest.cpp @@ -15,6 +15,9 @@ #include "gtest/gtest.h" #include #include +#include +#include +#include #define DEBUG_TYPE "libclang-test" TEST(libclang, clang_parseTranslationUnit2_InvalidArgs) { @@ -349,21 +352,25 @@ clang_ModuleMapDescriptor_dispose(MMD); } -class LibclangReparseTest : public ::testing::Test { +class LibclangParseTest : public ::testing::Test { std::set Files; + typedef std::unique_ptr fixed_addr_string; + std::map UnsavedFileContents; public: std::string TestDir; CXIndex Index; CXTranslationUnit ClangTU; unsigned TUFlags; + std::vector UnsavedFiles; void SetUp() override { llvm::SmallString<256> Dir; ASSERT_FALSE(llvm::sys::fs::createUniqueDirectory("libclang-test", Dir)); TestDir = Dir.str(); TUFlags = CXTranslationUnit_DetailedPreprocessingRecord | - clang_defaultEditingTranslationUnitOptions(); + clang_defaultEditingTranslationUnitOptions(); Index = clang_createIndex(0, 0); + ClangTU = nullptr; } void TearDown() override { clang_disposeTranslationUnit(ClangTU); @@ -384,6 +391,64 @@ OS << Contents; assert(OS.good()); } + void MapUnsavedFile(const char* name, const std::string &contents) { + auto it = UnsavedFileContents.emplace( + fixed_addr_string(new std::string(name)), + fixed_addr_string(new std::string(contents))); + UnsavedFiles.push_back({ + it.first->first->c_str(), // filename + it.first->second->c_str(), // contents + it.first->second->size() // length + }); + } + template + void Traverse(const F &TraversalFunctor) { + CXCursor TuCursor = clang_getTranslationUnitCursor(ClangTU); + std::reference_wrapper FunctorRef = std::cref(TraversalFunctor); + clang_visitChildren(TuCursor, + &TraverseStateless>, + &FunctorRef); + } + +private: + template + static CXChildVisitResult TraverseStateless(CXCursor cx, CXCursor parent, + CXClientData data) { + TState *State = static_cast(data); + return State->get()(cx, parent); + } +}; + +TEST_F(LibclangParseTest, SpellingLocation) { + MapUnsavedFile("main.cpp", + "#define BANANAS 4011\n" + "int plu = BANANAS;"); + + ClangTU = clang_parseTranslationUnit(Index, "main.cpp", nullptr, 0, + UnsavedFiles.data(), UnsavedFiles.size(), TUFlags); + + bool sawInt = false; + Traverse([&](CXCursor cx, CXCursor) { + if (cx.kind == CXCursor_IntegerLiteral) { + sawInt = true; + auto cxl = clang_getCursorLocation(cx); + CXFile expansionFile, spellingFile; + unsigned line, column, offset; + clang_getSpellingLocation(cxl, &expansionFile, &line, nullptr, nullptr); + EXPECT_EQ(2, line); // getSpellingLocation returns expansion location + clang_getRealSpellingLocation(cxl, &spellingFile, &line, &column, &offset); + EXPECT_TRUE(clang_File_isEqual(expansionFile, spellingFile)); + EXPECT_EQ(1, line); + EXPECT_EQ(17, column); + EXPECT_EQ(16, offset); + } + return CXChildVisit_Recurse; + }); + EXPECT_TRUE(sawInt); +} + +class LibclangReparseTest : public LibclangParseTest { +public: void DisplayDiagnostics() { unsigned NumDiagnostics = clang_getNumDiagnostics(ClangTU); for (unsigned i = 0; i < NumDiagnostics; ++i) {