diff --git a/include/Compiler/Clang.h b/include/Compiler/Clang.h index 61153468..75a01b2d 100644 --- a/include/Compiler/Clang.h +++ b/include/Compiler/Clang.h @@ -8,6 +8,8 @@ #include #include +#include + namespace std { template <> diff --git a/include/Compiler/Compiler.h b/include/Compiler/Compiler.h index d13ad2d7..59f4f610 100644 --- a/include/Compiler/Compiler.h +++ b/include/Compiler/Compiler.h @@ -1,7 +1,9 @@ #pragma once #include +#include #include + #include namespace clice { @@ -13,9 +15,11 @@ public: ASTInfo(std::unique_ptr action, std::unique_ptr instance, - std::unique_ptr tokBuf) : - action(std::move(action)), instance(std::move(instance)), tokBuf_(std::move(tokBuf)) { - resolver_ = std::make_unique(this->instance->getSema()); + std::unique_ptr tokBuf, + llvm::DenseMap&& directives) : + action(std::move(action)), instance(std::move(instance)), m_TokBuf(std::move(tokBuf)), + m_Directives(std::move(directives)) { + m_Resolver = std::make_unique(this->instance->getSema()); } ASTInfo(const ASTInfo&) = delete; @@ -50,19 +54,38 @@ public: } clang::syntax::TokenBuffer& tokBuf() { - assert(tokBuf_ && "Token buffer is not available"); - return *tokBuf_; + assert(m_TokBuf && "Token buffer is not available"); + return *m_TokBuf; } TemplateResolver& resolver() { - return *resolver_; + return *m_Resolver; + } + + auto& directives() { + return m_Directives; + } + + Directive& directive(clang::FileID id) { + return m_Directives[id]; + } + + /// Get the length of the token at the given location. + auto getTokenLength(clang::SourceLocation loc) { + return clang::Lexer::MeasureTokenLength(loc, srcMgr(), instance->getLangOpts()); + } + + /// Get the spelling of the token at the given location. + llvm::StringRef getTokenSpelling(clang::SourceLocation loc) { + return llvm::StringRef(srcMgr().getCharacterData(loc), getTokenLength(loc)); } private: std::unique_ptr action; std::unique_ptr instance; - std::unique_ptr tokBuf_; - std::unique_ptr resolver_; + std::unique_ptr m_TokBuf; + std::unique_ptr m_Resolver; + llvm::DenseMap m_Directives; }; struct PCHInfo { diff --git a/include/Compiler/Directive.h b/include/Compiler/Directive.h index 7e1c7d10..c03424b7 100644 --- a/include/Compiler/Directive.h +++ b/include/Compiler/Directive.h @@ -4,63 +4,68 @@ namespace clice { -/// Represents a full conditional preprocessing block, from #if to #endif. -struct IfBlock { - /// Represents a single conditional branch (e.g., #if, #ifdef, #elif). - struct Branch { - /// Location of the directive. - /// NOTE: The `#` is a separate token and not necessarily adjacent. - clang::SourceLocation location; +struct Include {}; - /// Location of the condition expression. - clang::SourceLocation condition; - - /// Evaluated value of the condition. - clang::PPCallbacks::ConditionValueKind value; +struct Condition { + enum BranchKind : uint8_t { + If = 0, + Elif, + Ifdef, + Elifdef, + Ifndef, + Elifndef, + Else, + EndIf, }; - /// Initial #if, #ifdef, or #ifndef directive. - Branch if_; + enum ConditionValue : uint8_t { + True = 0, + False, + Skipped, + None, + }; - /// #elif, #elifdef, or #elifndef directives. - std::vector elifs; + /// Kind of the branch. + BranchKind kind; - /// Location of the #else directive, if present. - clang::SourceLocation elseLoc; + /// Value of the condition. + ConditionValue value; - /// Location of the #endif directive. - clang::SourceLocation endifLoc; + /// Location of the directive identifier. + clang::SourceLocation loc; + + /// Range of the condition. + clang::SourceRange conditionRange; }; +struct MacroRef { + enum Kind : uint8_t { + Def = 0, + Ref, + Undef, + }; + + /// Kind of the macro reference. + Kind kind; + + /// The location of the macro name. + clang::SourceLocation loc; + + /// The macro definition information. + const clang::MacroInfo* macro; +}; + +/// Do we need to store pragma information? +struct Pragma {}; + struct Directive { - /// map from the location of if/ifdef/ifndef to the corresponding if block. - llvm::DenseMap ifBlocks; -}; + std::vector includes; + std::vector conditions; + std::vector macros; -struct Directives { - clang::Preprocessor& preproc; - clang::SourceManager& sourceManager; - llvm::DenseMap x; - - clang::CommentHandler* handler(); - std::unique_ptr callback(); + /// Tell preprocessor to collect directives information and store them in `directives`. + static void attach(clang::Preprocessor& pp, + llvm::DenseMap& directives); }; } // namespace clice - -namespace clice2 { - -struct Directive { - -}; - -// A class that record detailed information about preprocessing, like `TokenBuffer`. -class PPBuffer { -private: - llvm::DenseMap directives; -}; - -class PPCollector : public clang::PPCallbacks { -}; - -} // namespace clice2 diff --git a/src/Compiler/Compiler.cpp b/src/Compiler/Compiler.cpp index 9ef8e1d7..c4938baf 100644 --- a/src/Compiler/Compiler.cpp +++ b/src/Compiler/Compiler.cpp @@ -66,8 +66,7 @@ void applyPreamble(clang::CompilerInstance& instance, CompliationParams& params) } llvm::Expected ExecuteAction(std::unique_ptr instance, - clang::frontend::ActionKind kind, - bool collectPP = true) { + clang::frontend::ActionKind kind) { std::unique_ptr action; if(kind == clang::frontend::ActionKind::ParseSyntaxOnly) { action = std::make_unique(); @@ -88,29 +87,37 @@ llvm::Expected ExecuteAction(std::unique_ptr i return error("Failed to begin source file"); } + auto& pp = instance->getPreprocessor(); // FIXME: clang-tidy, include-fixer, etc? // `BeginSourceFile` may create new preprocessor, so all operations related to preprocessor // should be done after `BeginSourceFile`. - if(collectPP) { - auto& PP = instance->getPreprocessor(); - clang::syntax::TokenCollector collector{PP}; - if(auto error = action->Execute()) { - return clice::error("Failed to execute action, because {} ", error); - } + /// Collect directives. + llvm::DenseMap directives; + Directive::attach(pp, directives); - auto tokBuf = std::make_unique(std::move(collector).consume()); - tokBuf->indexExpandedTokens(); + std::optional collector; - return ASTInfo(std::move(action), std::move(instance), std::move(tokBuf)); - } else { - if(auto error = action->Execute()) { - return clice::error("Failed to execute action, because {} ", error); - } - - return ASTInfo(std::move(action), std::move(instance), nullptr); + /// It is not necessary to collect tokens if we are running code completion. + /// And in fact will cause assertion failure. + if(!instance->hasCodeCompletionConsumer()) { + collector.emplace(pp); } + + if(auto error = action->Execute()) { + return clice::error("Failed to execute action, because {} ", error); + } + + std::unique_ptr tokBuf; + if(collector) { + tokBuf = std::make_unique(std::move(*collector).consume()); + } + + return ASTInfo(std::move(action), + std::move(instance), + std::move(tokBuf), + std::move(directives)); } } // namespace @@ -201,7 +208,7 @@ llvm::Expected compile(CompliationParams& params, clang::CodeCompleteCo applyPreamble(*instance, params); - return ExecuteAction(std::move(instance), clang::frontend::ActionKind::ParseSyntaxOnly, false); + return ExecuteAction(std::move(instance), clang::frontend::ActionKind::ParseSyntaxOnly); } } // namespace clice diff --git a/src/Compiler/Directive.cpp b/src/Compiler/Directive.cpp index bf942302..5abbc6d4 100644 --- a/src/Compiler/Directive.cpp +++ b/src/Compiler/Directive.cpp @@ -5,43 +5,64 @@ namespace clice { -struct CommentHandler : public clang::CommentHandler { - Directives& directive; +namespace { - CommentHandler(Directives& directive) : directive(directive) {} +struct PPCallback : public clang::PPCallbacks { + clang::Preprocessor& pp; + llvm::DenseMap& directives; + llvm::DenseMap macroCache; - virtual bool HandleComment(clang::Preprocessor& preproc, clang::SourceRange Comment) { - // directive.comments.push_back(Comment); - auto start = directive.sourceManager.getCharacterData(Comment.getBegin()); - auto end = directive.sourceManager.getCharacterData(Comment.getEnd()); - // llvm::outs() << "Comment: " << llvm::StringRef(start, end - start) << "\n"; - return false; - } -}; + PPCallback(clang::Preprocessor& pp, llvm::DenseMap& directives) : + pp(pp), directives(directives) {} -struct PragmaHandler : public clang::PragmaHandler { - virtual void HandlePragma(clang::Preprocessor& PP, - clang::PragmaIntroducer Introducer, - clang::Token& FirstToken) { - // TODO: + void addCondition(clang::SourceLocation loc, + Condition::BranchKind kind, + Condition::ConditionValue value, + clang::SourceRange conditionRange) { + auto& directive = directives[pp.getSourceManager().getFileID(loc)]; + directive.conditions.emplace_back(Condition{kind, value, loc, conditionRange}); } - /// getIfNamespace - If this is a namespace, return it. This is equivalent to - /// using a dynamic_cast, but doesn't require RTTI. - virtual clang::PragmaNamespace* getIfNamespace() { - return nullptr; + void addCondition(clang::SourceLocation loc, + Condition::BranchKind kind, + clang::PPCallbacks::ConditionValueKind value, + clang::SourceRange conditionRange) { + Condition::ConditionValue condValue = Condition::None; + switch(value) { + case clang::PPCallbacks::CVK_False: { + condValue = Condition::False; + break; + } + case clang::PPCallbacks::CVK_True: { + condValue = Condition::True; + break; + } + case clang::PPCallbacks::CVK_NotEvaluated: { + condValue = Condition::Skipped; + break; + } + } + addCondition(loc, kind, condValue, conditionRange); } -}; -struct PPCallback : clang::PPCallbacks { - PPCallback(clang::Preprocessor& preproc) : - preproc(preproc), srcMgr(preproc.getSourceManager()) {} + void addCondition(clang::SourceLocation loc, + Condition::BranchKind kind, + const clang::Token& name, + const clang::MacroDefinition& definition) { + if(auto def = definition.getMacroInfo()) { + addMacro(def, MacroRef::Ref, name.getLocation()); + addCondition(loc, kind, Condition::True, name.getLocation()); + } else { + addCondition(loc, kind, Condition::False, name.getLocation()); + } + } - clang::Preprocessor& preproc; - clang::SourceManager& srcMgr; - ; - clang::FileID currentID; + void addMacro(const clang::MacroInfo* def, MacroRef::Kind kind, clang::SourceLocation loc) { + auto& directive = directives[pp.getSourceManager().getFileID(loc)]; + directive.macros.emplace_back(MacroRef{kind, loc, def}); + } +public: void FileChanged(clang::SourceLocation loc, clang::PPCallbacks::FileChangeReason reason, clang::SrcMgr::CharacteristicKind fileType, @@ -57,122 +78,103 @@ struct PPCallback : clang::PPCallbacks { llvm::StringRef RelativePath, const clang::Module* SuggestedModule, bool ModuleImported, - clang::SrcMgr::CharacteristicKind FileType) override { - // TODO: record all include files - llvm::SmallVector RealPath; - // fs::make_absolute(SearchPath + "/" + RelativePath, RealPath); - // path::remove_dots(RealPath, /*remove_dot_dot=*/true); - // llvm::outs() << RealPath << "\n"; - } + clang::SrcMgr::CharacteristicKind FileType) override {} void PragmaDirective(clang::SourceLocation Loc, - clang::PragmaIntroducerKind Introducer) override { - // llvm::outs() << "PragmaDirective\n"; - } + clang::PragmaIntroducerKind Introducer) override {} - void If(clang::SourceLocation Loc, - clang::SourceRange ConditionRange, - clang::PPCallbacks::ConditionValueKind ConditionValue) override { - // llvm::outs() << "If\n"; + void If(clang::SourceLocation loc, + clang::SourceRange conditionRange, + clang::PPCallbacks::ConditionValueKind value) override { + addCondition(loc, Condition::If, value, conditionRange); } void Elif(clang::SourceLocation loc, clang::SourceRange conditionRange, - clang::PPCallbacks::ConditionValueKind conditionValue, - clang::SourceLocation ifLoc) override {} + clang::PPCallbacks::ConditionValueKind value, + clang::SourceLocation) override { + addCondition(loc, Condition::Elif, value, conditionRange); + } void Ifdef(clang::SourceLocation loc, const clang::Token& name, - const clang::MacroDefinition& definition) override {} + const clang::MacroDefinition& definition) override { + addCondition(loc, Condition::Ifdef, name, definition); + } + /// Invoke when #elifdef branch is taken. void Elifdef(clang::SourceLocation loc, const clang::Token& name, - const clang::MacroDefinition& definition) override {} + const clang::MacroDefinition& definition) override { + addCondition(loc, Condition::Elifdef, name, definition); + } + /// Invoke when #elif is skipped. void Elifdef(clang::SourceLocation loc, clang::SourceRange conditionRange, - clang::SourceLocation ifLoc) override {} + clang::SourceLocation) override { + /// FIXME: should we try to evaluate the condition to compute the macro reference? + addCondition(loc, Condition::Elifdef, Condition::Skipped, conditionRange); + } + /// Invoke when #ifndef is taken. void Ifndef(clang::SourceLocation loc, const clang::Token& name, - const clang::MacroDefinition& definition) override {} + const clang::MacroDefinition& definition) override { + addCondition(loc, Condition::Ifndef, name, definition); + } - // invoke when #elifndef is taken + // Invoke when #elifndef is taken. void Elifndef(clang::SourceLocation loc, const clang::Token& name, - const clang::MacroDefinition& definition) override {} + const clang::MacroDefinition& definition) override { + addCondition(loc, Condition::Elifndef, name, definition); + } - // invoke when #elifndef is skipped + // Invoke when #elifndef is skipped. void Elifndef(clang::SourceLocation loc, clang::SourceRange conditionRange, - clang::SourceLocation ifLoc) override {} + clang::SourceLocation) override { + addCondition(loc, Condition::Elifndef, Condition::Skipped, conditionRange); + } - void Else(clang::SourceLocation loc, clang::SourceLocation ifLoc) override {} + void Else(clang::SourceLocation loc, clang::SourceLocation ifLoc) override { + addCondition(loc, Condition::Else, Condition::None, clang::SourceRange()); + } - void Endif(clang::SourceLocation loc, clang::SourceLocation ifLoc) override {} + void Endif(clang::SourceLocation loc, clang::SourceLocation ifLoc) override { + addCondition(loc, Condition::EndIf, Condition::None, clang::SourceRange()); + } - void MacroDefined(const clang::Token& MacroNameTok, const clang::MacroDirective* MD) override { - if(MD) { - auto info = MD->getMacroInfo(); - // llvm::outs() << "is builtin: " << info->isBuiltinMacro() << "\n"; - if(!info->isBuiltinMacro()) { - auto location = MacroNameTok.getLocation(); - if(!srcMgr.isWrittenInBuiltinFile(location) && - !srcMgr.isWrittenInCommandLineFile(location)) { - MacroNameTok.getLocation().dump(srcMgr); - // srcMgr.getIncludeLoc(srcMgr.getFileID(MacroNameTok.getLocation())).dump(srcMgr); - llvm::outs() << preproc.getSpelling(MacroNameTok) << "\n"; - } - auto def = MD->getDefinition(); - } - // MD->dump(); + void MacroDefined(const clang::Token& name, const clang::MacroDirective* MD) override { + if(auto def = MD->getMacroInfo()) { + addMacro(def, MacroRef::Def, name.getLocation()); } } - void MacroExpands(const clang::Token& MacroNameTok, - const clang::MacroDefinition& MD, - clang::SourceRange Range, - const clang::MacroArgs* Args) override { - auto info = MD.getMacroInfo(); - llvm::outs() << "------------------------\n"; - // auto info = MD.getMacroInfo(); - if(info->isObjectLike()) { - Range = clang::SourceRange(MacroNameTok.getLocation(), MacroNameTok.getEndLoc()); - } else if(info->isFunctionLike()) { - // MacroNameTok.getLocation().dump(srcMgr); + void MacroExpands(const clang::Token& name, + const clang::MacroDefinition& definition, + clang::SourceRange range, + const clang::MacroArgs* args) override { + if(auto def = definition.getMacroInfo()) { + addMacro(def, MacroRef::Ref, name.getLocation()); } - Range.dump(srcMgr); - auto s = srcMgr.getCharacterData(Range.getBegin()); - auto e = srcMgr.getCharacterData(Range.getEnd()); - llvm::outs() << llvm::StringRef(s, e - s) << "\n"; - - llvm::outs() << preproc.getSpelling(MacroNameTok) << " "; - if(Args) { - auto size = Args->getNumMacroArguments(); - for(int i = 0; i < size; i++) { - auto arg = Args->getUnexpArgument(i); - auto len = Args->getArgLength(arg); - for(int j = 0; j < len; j++) { - llvm::outs() << preproc.getSpelling(arg[j]) << " "; - } - } - } - llvm::outs() << "\n"; } - void MacroUndefined(const clang::Token& MacroNameTok, + void MacroUndefined(const clang::Token& name, const clang::MacroDefinition& MD, - const clang::MacroDirective* Undef) override { - // TODO: + const clang::MacroDirective* undef) override { + if(auto def = MD.getMacroInfo()) { + addMacro(def, MacroRef::Undef, name.getLocation()); + } } }; -clang::CommentHandler* Directives::handler() { - // return new CommentHandler(*this); -} +} // namespace -std::unique_ptr Directives::callback() { - // return std::make_unique(*this); +void Directive::attach(clang::Preprocessor& pp, + llvm::DenseMap& directives) { + pp.addPPCallbacks(std::make_unique(pp, directives)); } } // namespace clice diff --git a/tests/Compiler/test.cpp b/tests/Compiler/test.cpp new file mode 100644 index 00000000..d7743cab --- /dev/null +++ b/tests/Compiler/test.cpp @@ -0,0 +1,21 @@ +#define name 1 + +#if name + +#elif name + +#else + +#endif + +#undef name + +#define name 1 + +#if name + +#elif name + +#else + +#endif \ No newline at end of file diff --git a/tests/Index/test.cpp b/tests/Index/test.cpp index f2c17e0d..f6c7334b 100644 --- a/tests/Index/test.cpp +++ b/tests/Index/test.cpp @@ -1,13 +1,3 @@ +#ifdef name - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - +#endif diff --git a/unittests/Compiler/Directive.cpp b/unittests/Compiler/Directive.cpp new file mode 100644 index 00000000..39d8f240 --- /dev/null +++ b/unittests/Compiler/Directive.cpp @@ -0,0 +1,54 @@ +#include "../Test.h" +#include "Compiler/Compiler.h" + +namespace clice { + +TEST(Compiler, Directive) { + + const char* code = R"cpp( +#include + +int main(){ + printf("Hello world"); + return 0; +} +)cpp"; + + foreachFile("Compiler", [](std::string name, llvm::StringRef content) { + llvm::SmallVector compileArgs = { + "clang++", + "-std=c++20", + name.c_str(), + "-resource-dir", + "/home/ykiko/C++/clice2/build/lib/clang/20", + }; + + CompliationParams params; + params.path = name.c_str(); + params.content = content; + params.args = compileArgs; + + auto info = compile(params); + ASSERT_TRUE(bool(info)); + + for(auto& [id, file]: info->directives()) { + for(auto& condition: file.conditions) { + print("kind: {}, value: {}, loc: {}, range: {}\n", + support::enum_name(condition.kind), + support::enum_name(condition.value), + condition.loc.printToString(info->srcMgr()), + condition.conditionRange.printToString(info->srcMgr())); + } + + for(auto& macro: file.macros) { + print("name: {}, kind: {}, loc: {}, def: {}\n", + info->getTokenSpelling(macro.loc), + support::enum_name(macro.kind), + macro.loc.printToString(info->srcMgr()), + static_cast(macro.macro)); + } + } + }); +} + +} // namespace clice