Clang之詞法分析Lex


    Clang是LLVM編譯器框架的前端(Frontend)編譯器,可編譯鏈接C、C++、Objective-C和Objective-C++四種語言的項目代碼。Clang 的開發目標是提供一個可以替代 GCC 的前端編譯器,與GCC相比,節省時間和內存空間;擁有更人性化的代碼診斷輸出;基於庫的框架,使編譯和鏈接過程模塊化;方便集成進IDE等等(具體參見calng源碼目錄clang/www/comparison.html, clang都指源碼目錄,以下都同此)。從開發角度,GCC或G++代碼龐大,代碼耦合度高,“是一個純粹的編譯系統”,非常不利於二次開發。LLVM+Clang則不同,可謂開發者的編譯器。

1、何為前端(Frontend)

    早期,我們將編譯器描述為一個簡單的盒子,能夠將源程序轉化為目標程序即可…單盒模型指出,編譯器必須理解源程序並將功能映射到目標機。這兩項任務截然不同的性質暗示着一種可能的任務划分,並最終導致了一種將編譯分解為兩個主要部分的設計:前端和后端。(摘自《編譯器設計》,Keith D.Cooper  Linda Torczon著)。LLVM就是如上所說之后端,Clang就是前端。以下論述中llvm的版本為3.6.0。

2、三步演繹

    編譯過程一般分為詞法分析、語法分析和語義分析,clang也不例外:

    clang通過Preprocessor詞法分析出一個一個 Token;

    語法分析可得AST(Abstract Syntax Tree,抽象語法樹),clang提供了訪問者Visistor和各種回調函數供用戶使用;

    Clang基本支持C系列的標准,在語義分析之上——靜態代碼分析層面,通過注冊機制,只需實現ExplodedGraph提供Checker 和 CheckerVisitor接口就可實現高級語義檢查。

3、  詞法分析(Lex)

    Clang詞法分析的核心數據結構是預編譯器Preprocessor,循環調用Preprocessor.Lex(Token)可解析出一個一個Token。使用Proprocessor需要一大堆的初始化函數,首先從構造函數着手。

3.1  構造函數說明

Preprocessor(IntrusiveRefCntPtr<PreprocessorOptions> PPOpts,

                           DiagnosticsEngine &diags, LangOptions &opts,

                           SourceManager &SM, HeaderSearch &Headers,

                           ModuleLoader &TheModuleLoader,

                           IdentifierInfoLookup *IILookup, bool OwnsHeaders,

                           TranslationUnitKind TUKind)

(1)PreprocessorOptions

    為Preprocess初始化做准備,主要設置預處理選項,如提供宏參數(-Dbug)、重映射文件說明(Remappedfile)等;

(2)DiagnosticsEngine

    Diagnostics存放於源代碼clang/lib/Basic中,足說明它是一個很基礎的類,貫穿整個clang。DiagnosticsEngine是供前端報告錯誤、警告、提示等消息的具體類,它需要一個翻譯單元和位置管理器(DiagnosticsEngine is tied to one translation unit and one SourceManager,因為一般編譯打印的診斷信息主要就是錯誤的位置、錯誤類型)。

    它有一個成員函數Report成員函數,可以觸發各種Diagnositc信息,還有一個回調的類DiagnosticConsumer處理觸發的各種Diagnositc。

 

inline DiagnosticBuilder Report(SourceLocation Loc, unsigned DiagID);

  inline DiagnosticBuilder Report(unsigned DiagID);

    其構造函數如下,下面一一說明。

DiagnosticsEngine(const IntrusiveRefCntPtr<DiagnosticIDs> &Diags,

                      DiagnosticOptions *DiagOpts,

                      DiagnosticConsumer *client = nullptr,

                      bool ShouldOwnClient = true);

(2-1)DiagnosticIDs

    所有代碼的診斷(Diagnostic)信息的種類和輸出方式等都在這里實現。Clang/include/clang/Basic下 DiagnosticCommonKinds.inc, DiagnosticDriverKinds.inc, DiagnosticFrontendKinds.inc, DiagnosticSerializationKinds.inc, DiagnosticLexKinds.inc, DiagnosticParseKinds.inc, DiagnosticASTKinds.inc, DiagnosticCommentKinds.inc, DiagnosticSemaKinds.inc, DiagnosticAnalysisKinds.inc列舉了各種診斷信息。

(2-2)DiagnosticOptions

    Diagnostic的選項設置。

(2-3)DiagnosticConsumer

    該類是一個回調類,對已經得到的診斷(Diagnostic)信息,做再次處理,主要作用是以定制的方式呈現給外界。Clang中實現了DiagnosticConsumer的繼承類,如IgnoringDiagConsumer, LogDiagnosticPrinter,TextDiagnosticPrinter,ForwardingDiagnosticConsumer等,如下簡單打印是第幾個warnings:

class clientConsumer:public DiagnosticConsumer{

void HandleDiagnostic(DiagnosticsEngine::Level Level,

                                            const Diagnostic &Info) {

DiagnosticConsumer::HandleDiagnostic(Level, Info);

        std::cout<<getNumWarnings()<<"th warnings occurs!"<<std::endl;//當warning出現時,打印輸出。}};

Clang中的診斷Diagnostic等級有六類,如下枚舉:

enum Level {

    Ignored = DiagnosticIDs::Ignored,

    Note = DiagnosticIDs::Note,

    Remark = DiagnosticIDs::Remark,

    Warning = DiagnosticIDs::Warning,

    Error = DiagnosticIDs::Error,

    Fatal = DiagnosticIDs::Fatal

  };

(2-4)ShouldOwnClient

    當設置為True時,可以轉移DiagnosticsEngine 的控制權(具體可以參考DiagnosticsEngine的takeClient()成員函數)。

(3)LangOptions

    主要是關於C/C++/Objective-c/objective-c++語言的一些選項。

(4)SourceManager

    SourceManager也位於clang/lib/Basic中,是資源管理的工具箱(Track and cache source files)。除cache、buffer管理之外,還管理SourceLocation。

(4-1)SourceLocation

      SourceLocation:Encodes a location in the source. The SourceManager can decode this to get at the full include stack, line and column information.在編譯器中需要用到的三個“重要”Location:行號,列號,聲明和調用文件路徑,都與SourceManager有關,其中行號和列號用SourceLocation表示。SourceLocation是一個偏移,整個type大小為四個字節(4==sizeof(SourceLocation))。SourceRange是兩個SourceLocation組成的區間。

(4-2)Location種類

    Location有三種類型:spelling Location,expansion Location,presumed location。

    當遇到宏展開的時候,expansion和presumed解析方式一樣,行的結果可能不能一樣(因為presumed遇到#line會重新計算行號),列結果一樣都是call的列;而spelling是其原始定義(#define)的行和列。

    當遇到#line指定行號之后的代碼,spelling和expansion結果一樣(非宏定義的地方),而presumed會重新計算行號。如下簡單說明,第六行的數字2的位置spelling是第一行,而expansion則是其展開的位置第六行,presumed因為前面有#line 4則表示第四行。

1#define min(x,y) (x)>(y)?(y):(x)

23:Void main(int argc,char **argv){

4:

5#line 4

6:int a=min(2,4);

//解析數字2的位置:|spelling |expansion|presumed|

//                   |  1行    |  6行    |   4行  |

9:}      

    調用SourceManager的getSpellingLineNumber(SourceLocation)獲得行號,getSpellingColumNumber(SourceLocation)獲得列號。其他兩種Location類似。

(4-3)Token所在的文件

    Preprocessor不僅處理cpp文件中的Token,而且還處理#include的Token。如果此Token屬於#include文件,可以使用sourceManager的getBufferName成員函數。如下示例:

if(!SourceMgr_->isInMainFile(tok.getLocation()))

std::cout<<SourceMgr_->getBufferName(tok.getLocation())<<std::endl;

     如果是在MainFile中(即translation unit中的CPP文件),可以通過SourceManager的getPresumedLoc(SourceLocation)獲取PresumedLoc,該類中有相關filename。

FileID fd=SourceMgr_->getMainFileID();

    if (!fd.isInvalid()){

         std::cout<<"main file:";

         const FileEntry * FE=SourceMgr_->getFileEntryForID(fd);

            if (FE && FE->isValid())

          std::cout<<FE->getName()<<std::endl;

}

(5)HeaderSearch

    提供頭文件的搜尋位置,其AddSearchPath成員函數可以為頭文件搜索提供新的路徑,當AddSearchPath第二個參數設置為True,則會覆蓋原有路徑;如果為false,則為添加。

const DirectoryEntry *DE = FileMgr.getDirectory(SearchPath);

     if(DE){

      DirectoryLookup DL(DE, SrcMgr::C_User, false);

     HeaderInfo->AddSearchPath(DL, false);

}

(6)ModuleLoader

    主要預處理Objective-C語言代碼中@import指令。在clang/doc/Modules.rst中,大篇幅談及了import加載模塊的好處,將向C++委員會建議加入此功能。

(7)IdentifierInfoLookup

    詞法解析(Lex)每個Token,都有與之對應TokenKinds,Identifier是TokenKinds的其中一種(include/clang/Basic/TokenKinds.def有說明),主要是指函數或者變量名等。IdentifierInfoLookup 是個抽象接口【virtual IdentifierInfo* get(StringRef Name) 必須實現】,在Preprocessor構造函數中,如果該項不為NULL,預處理器在查詢IdentiferInfo表(hash表)時,將優先調用IdentifierInfoLookup的get成員函數,獲取IdentifierInfo,這樣就可以達到修改Token的IdentifierInfo屬性的目的。

(8)OwnsHeaders

    如果前面的HeaderSearch是動態分配的,該項設置為true,則Preprocessor會回收該空間。無需用戶調用delete。

(9)TranslationUnitKind

    每一個Cpp及include文件組成一個翻譯單元(Translation unit),在Preprocessor中默認為TU_Complete,表示是一個完整的翻譯單元,也沒有使用該參數。

enum TranslationUnitKind {

  /// \brief The translation unit is a complete translation unit.

  TU_Complete,

  /// \brief The translation unit is a prefix to a translation unit, and is

  /// not complete.

  TU_Prefix,

  /// \brief The translation unit is a module.

  TU_Module

};

 

  至此,Preprocessor的構造函數說明,在使用之前只需要做些繁瑣的初始化工作。

3.2  Preprocessor鈎子

    在預處理translation unit完每一部分(如#include、#if)的時候,還可以往Preprocessor中添加“鈎子”( 繼承PPCallbacks,實現某些接口函數,然后addPPCallbacks),就可以將用戶的“意圖”加入到Preprocessor的處理過程中。這些可以接口函數參考clang/Lex/PPCallbacks.h文件。如下示例實現了InclusionDirective接口,打印#include文件的搜索路徑。

/*該回調函數打印#include文件的搜索路徑*/

class InclusionDirectiveCallbacks : public PPCallbacks {

public:

  void InclusionDirective(SourceLocation HashLoc,

    const Token &IncludeTok,

    StringRef FileName,

    bool IsAngled,

    CharSourceRange FilenameRange,

    const FileEntry *File,

    StringRef SearchPath,

    StringRef RelativePath,

    const Module *Imported) {

          std::cout<< FileName.str()<<std::endl;

     std::cout<<SearchPath.str()<<std::endl;  }};

    在clang中源代碼中有PPConditionalDirectiveRecord和PreprocessingRecord——兩個Preprocess的Hooks,以PPConditionalDirectiveRecord為例,監聽Preprocess處理#If,#Ifdef,#Ifndef,#Elif,#Else,#Endif。

PPConditionalDirectiveRecord *callbacks2=new PPConditionalDirectiveRecord(*SourceMgr_);

    PP.addPPCallbacks(std::unique_ptr<PPCallbacks>(callbacks2));

….

//測試一個代碼位置區間是否與#If,#Ifdef,#Ifndef,#Elif,#Else,#Endif等模塊有交集

bool ret=callbacks2->rangeIntersectsConditionalDirective(SourceRange(innerdefiner,outdefiner));

3.3整個代碼

(1)makefile

LLVM基礎庫眾多,而且找對應庫非常麻煩。借助llvm-config工具,雖然降低了編譯速度,但使用簡單。以下用clang++編譯LexerTest.cpp。

CXX := clang++

LLVMCOMPONENTS := cppbackend

RTTIFLAG := -fno-rtti

LLVMCONFIG := llvm-config

 

CXXFLAGS := -I. -I/usr/local/include -I/usr/include -I$(shell $(LLVMCONFIG) --src-root)/tools/clang/include -I$(shell $(LLVMCONFIG) --obj-root)/tools/clang/include -g  $(shell $(LLVMCONFIG) --cxxflags) $(RTTIFLAG)

LLVMLDFLAGS := $(shell $(LLVMCONFIG) --ldflags --libs $(LLVMCOMPONENTS))

 

SOURCES =    LexerTest.cpp

 

OBJECTS = $(SOURCES:.cpp=.o)

EXES = $(OBJECTS:.o=)

CLANGLIBS = \

               -lclangTooling\

               -lclangFrontendTool\

               -lclangFrontend\

               -lclangDriver\

               -lclangSerialization\

               -lclangCodeGen\

               -lclangParse\

               -lclangSema\

               -lclangStaticAnalyzerFrontend\

               -lclangStaticAnalyzerCheckers\

               -lclangStaticAnalyzerCore\

               -lclangAnalysis\

               -lclangARCMigrate\

               -lclangRewriteFrontend\

               -lclangRewrite\

               -lclangEdit\

               -lclangAST\

               -lclangLex\

               -lclangBasic\

               $(shell $(LLVMCONFIG) --libs)\

               $(shell $(LLVMCONFIG) --system-libs)

 

#all: $(OBJECTS)

#$(EXES)

%.o:%.cpp

$(CXX) $(CXXFLAGS) -c -o $@ $< 

 

#%: %.o

LexerTest: LexerTest.o

$(CXX)  -o $@ *.o  $(CLANGLIBS) $(LLVMLDFLAGS)

#FrontendAction:FrontendAction.o

clean:

-rm -f $(EXES) $(OBJECTS)
View Code

 

(2)源代碼

#include "clang/Lex/Lexer.h"

#include "clang/Basic/Diagnostic.h"

#include "clang/Basic/DiagnosticOptions.h"

#include "clang/Basic/FileManager.h"

#include "clang/Basic/LangOptions.h"

#include "clang/Basic/SourceManager.h"

#include "clang/Basic/TargetInfo.h"

#include "clang/Basic/TargetOptions.h"

#include "clang/Lex/HeaderSearch.h"

#include "clang/Lex/HeaderSearchOptions.h"

#include "clang/Lex/ModuleLoader.h"

#include "clang/Lex/Preprocessor.h"

#include "clang/Lex/PreprocessorOptions.h"

#include "clang/Frontend/TextDiagnosticPrinter.h"

#include "clang/Lex/PPConditionalDirectiveRecord.h"

#include "llvm/Support/Path.h"

#include<iostream>

using namespace llvm;

using namespace clang;

 

class clientConsumer:public DiagnosticConsumer{

void HandleDiagnostic(DiagnosticsEngine::Level Level,

                                            const Diagnostic &Info) {

DiagnosticConsumer::HandleDiagnostic(Level, Info);

        std::cout<<getNumWarnings()<<"th warnings occurs!"<<std::endl;

}

};

std::string getSourceText_(Token Begin, Token End,    SourceManager * SourceMgr_,

    LangOptions & LangOpts);

class InclusionDirectiveCallbacks : public PPCallbacks {

public:

  void InclusionDirective(SourceLocation HashLoc,

    const Token &IncludeTok,

    StringRef FileName,

    bool IsAngled,

    CharSourceRange FilenameRange,

    const FileEntry *File,

    StringRef SearchPath,

    StringRef RelativePath,

    const Module *Imported) {

    

      std::cout<< “include file:”<<FileName.str()<<”::”;

     std::cout<<SearchPath.str()<<std::endl;

  }

};

 

class IDLookup :public IdentifierInfoLookup{

IdentifierInfo* get(StringRef Name){

return NULL;

}

 

};

 

class moduleImportCallback:public PPCallbacks{

public:

void moduleImport(SourceLocation ImportLoc,

                            ModuleIdPath Path,

                            const Module *Imported) {

if(Imported)

std::cout<<"import:"<<Imported->Name<<std::endl;

  }

 

 

 

};

 

class VoidModuleLoader : public ModuleLoader {

  ModuleLoadResult loadModule(SourceLocation ImportLoc,

                              ModuleIdPath Path,

                              Module::NameVisibilityKind Visibility,

                              bool IsInclusionDirective) override {

     std::cout<<"load Module:"<<std::endl;

    return ModuleLoadResult();

  }

 

  void makeModuleVisible(Module *Mod,

                         Module::NameVisibilityKind Visibility,

                         SourceLocation ImportLoc,

                         bool Complain) override { std::cout<<Mod->Name<<std::endl;}

 

  GlobalModuleIndex *loadGlobalModuleIndex(SourceLocation TriggerLoc) override

    {

    std::cout<<"loadGlobalModuleIndex"<<std::endl;

   return nullptr; }

  bool lookupMissingImports(StringRef Name, SourceLocation TriggerLoc) override

    {

     std::cout<<"lookupMissingImports"<<std::endl;

       return 0;}

  };

 

 bool  CheckLex(StringRef Source) {

    DiagnosticOptions diagnosticOptions;

    TextDiagnosticPrinter *pTextDiagnosticPrinter =

        new TextDiagnosticPrinter(

            llvm::errs(),

            &diagnosticOptions,

            true);

    IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());

    clientConsumer cC;

    DiagnosticsEngine *Diags =new DiagnosticsEngine(DiagID,

            &diagnosticOptions,

            //pTextDiagnosticPrinter);

          //new IgnoringDiagConsumer());

            &cC,false);

    FileSystemOptions FileMgrOpts;

    FileManager FileMgr(FileMgrOpts);

 

    SourceManager * SourceMgr_=  new SourceManager(*Diags,FileMgr);

    LangOptions LangOpts;

    clang::TargetOptions targetOptions;

   targetOptions.Triple = llvm::sys::getDefaultTargetTriple();

 

   IntrusiveRefCntPtr<TargetInfo>  Target = TargetInfo::CreateTargetInfo(*Diags,

            std::make_shared<clang::TargetOptions>(targetOptions));

  

    std::unique_ptr<MemoryBuffer> Buf = MemoryBuffer::getMemBuffer(Source);

    const FileEntry *File = FileMgr.getFile("./input.cpp");

    if(File){

     SourceMgr_->setMainFileID(SourceMgr_->createFileID(File,SourceLocation(),SrcMgr::C_User));

    }

    else

    SourceMgr_->setMainFileID(SourceMgr_->createFileID(std::move(Buf)));

 

    VoidModuleLoader ModLoader;

    HeaderSearch *HeaderInfo=new HeaderSearch(new HeaderSearchOptions, *SourceMgr_, *Diags, LangOpts,

                            Target.get());

       StringRef SearchPath = llvm::sys::path::parent_path("/home/usr/Desktop/Lex");

     const DirectoryEntry *DE = FileMgr.getDirectory(SearchPath);

      const DirectoryEntry *DE1 = FileMgr.getDirectory("/home/usr/Desktop/Lex");

     if(DE1&&DE){

      DirectoryLookup DL(DE, SrcMgr::C_User, false);

      DirectoryLookup DL1(DE1, SrcMgr::C_User, false);

     HeaderInfo->AddSearchPath(DL, false);

     HeaderInfo->AddSearchPath(DL1, true);}

     IDLookup *idlookup=new IDLookup;

    Preprocessor PP(new PreprocessorOptions(), *Diags, LangOpts, *SourceMgr_,

                    *HeaderInfo, ModLoader, /*IILookup =*/NULL,

                    /*OwnsHeaderSearch =*/true,TU_Prefix);

    PP.Initialize(*Target);

    moduleImportCallback *Callbacks1= new moduleImportCallback;

    PP.addPPCallbacks(std::unique_ptr<PPCallbacks>(Callbacks1));

InclusionDirectiveCallbacks *Callbacks=new InclusionDirectiveCallbacks;

    PP.addPPCallbacks(std::unique_ptr<PPCallbacks>(Callbacks));

    PPConditionalDirectiveRecord *callbacks2=new PPConditionalDirectiveRecord(*SourceMgr_);

    PP.addPPCallbacks(std::unique_ptr<PPCallbacks>(callbacks2));

    PP.EnterMainSourceFile();

    bool Invalid;

    bool islong=false;

    SourceLocation innerdefiner;

    SourceLocation outdefiner;

   

    while (1) {

      Token tok;

      PP.Lex(tok);

      if (tok.is(tok::eof))

        break;

      std::string str =getSourceText_(tok,tok,

                             SourceMgr_, LangOpts);

      if(str=="printf")

           Diags->Report(diag::warn_mt_message) << str;

      if(str=="innerDefiner")

           innerdefiner=tok.getLocation();

      if(str=="outDefiner")

           outdefiner=tok.getLocation();

      //std::cout<<str<<":"<<tok.getName()<<std::endl;

     //std::cout<<str<<std::endl;

   

  //std::cout<<Str.data()<<std::endl;

   /* std::cout<<str<<"::" \

             <<SourceMgr_->getExpansionLineNumber(tok.getLocation())<<":"  \

            <<SourceMgr_->getExpansionColumnNumber(tok.getLocation())<<std::endl;*/

std::cout<<str<<"----";

if(!SourceMgr_->isInMainFile(tok.getLocation()))

std::cout<<SourceMgr_->getBufferName(tok.getLocation())<<std::endl;

 

    }

    FileID fd=SourceMgr_->getMainFileID();

    if (!fd.isInvalid()){

         std::cout<<"main file:";

         const FileEntry * FE=SourceMgr_->getFileEntryForID(fd);

            if (FE && FE->isValid())

          std::cout<<FE->getName()<<std::endl;

    }

         bool ret=callbacks2->rangeIntersectsConditionalDirective(SourceRange(innerdefiner,outdefiner));

   if(ret)

   std::cout<<"Total Memory:"<<callbacks2->getTotalMemory()<<std::endl;

 

return true;

}

//}//end namespace clangT

 

std::string getSourceText_(Token Begin, Token End,    SourceManager * SourceMgr_,

    LangOptions & LangOpts) {

    bool Invalid;

    StringRef Str =

        Lexer::getSourceText(CharSourceRange::getTokenRange(SourceRange( \

                                    Begin.getLocation(), End.getLocation())),

                             *SourceMgr_, LangOpts, &Invalid);

    if (Invalid)

      return "<INVALID>";

   // std::cout<<Str.str()<<std::endl;

    return Str;

  }

 

 

 

int main(int argc,char **argv)

{

FileSystemOptions fileSystemOptions;

FileManager file(fileSystemOptions);

auto FileBuffer=file.getBufferForFile("./input.cpp");

std::string code= (*FileBuffer)->getBuffer();

 CheckLex(code);

}
全部代碼

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM