github, git, google: clang-前端插件-给各种无花括号的“块”加花括号-基于llvm15--clang-plugin-add-brace-----google镜像

处理的语句

case

术语约定或备忘

  1. case起止范围: 从冒号到下一个’case’开头, 简称有: case内 、case内容
  2. Ast: Abstract syntax tree: 抽象语法树

没插入花括号的case

若case内, 以下任一条成立,则 跳过该case 即 不会对该case内容用花括号包裹.

  • 有#define、
  • 有#include、
  • 有直属变量声明、
  • 空case、
  • 有宏调用

详述

预处理回调收集#include指令、宏定义

CollectIncMacro_PPCb:Collect Inlucde Macro PPCallbacks : 收集Inlucde和Macro的预处理回调

收集 #include、 #define , 以判断case起止范围内 有无 #include、 #define

遍历switch内某case起止范围内每条语句

RangeHasMacroAstVst: Range Has Macro Call Ast Vistor: 给定范围有无宏调用Ast遍历器

名义上遍历整个switch下的Stmt,实际遍历 给定范围内( 即 case起止范围 内) 的语句,进行以下计算:

1. hasMacro: case起止范围 有无宏调用,
  从而帮助过滤掉 有宏调用 的case
2. caseKSubStmtCnt:    case起止范围 语句个数(即 case子语句个数), 
  从而帮助 过滤掉 空case
3. VarDeclDirectlyInCaseKCnt:   直接写在'case'内的变量声明语句个数,
  即 直属变量声明个数 
  从而 帮助过滤掉 有直属变量声明 的case。
  直接写在'case'内的变量声明语句个数,包括以下两种情况:
  3.1. 直接写在'case'内,其父亲是case语句的
  3.2. 直接写在'case'内, 但是其父亲是switch块的.  
    即 存在 在case内的语句 但却不属于该case  而是直接属于switch, 此现象,直接导致 case的子语句 是伪命题,
      才使得 RangeHasMacroAstVst 不可能 实现无遗漏地 遍历 case下的子语句 , 
        只能扩大遍历范围到整个switch 并只关注case起止范围内的语句 才能 实现无遗漏地、精准地 遍历 case下的子语句。

实际运行花括号插件

到此 加花括号插件完工了,在llvm-project上正常运行:

sudo docker exec -it ubuntu2204_clang15Compile bash

弹出docker实例ubuntu2204_clang15Compile的bash命令行,以下命令都在此命令行下执行

cd /pubx/

git clone https://gitcode.net/pubz/llvm-project/-/commits/brc-dev-no_tick
#即 https://gitcode.net/pubz/llvm-project/-/commit/bee38a325d0957a28b4d06cb4be3c251d143cdf0
#克隆仓库llvm-project后目录结构如下: /pubx/llvm-project/.git/config
  • 步骤1: 对每个被直接编译的源文件中单语句加花括号

对llvm-project的每个源文件的编译过程应用插件libBrcPlugin.so 以 对 该源文件中单语句加花括号

source /pubx/llvm-project/doc_clang15_build/brc_build1_plugin.sh

brc_build1_plugin.sh

  • 步骤2: 对加了花括号后的llvm-project再次做正常的普通编译
source /pubx/llvm-project/doc_clang15_build/brc_build2_directly.sh

brc_build2_directly.sh

  • 步骤3: 验证
//编写c语言源文件 hello.c,内容如下:
#include <stdio.h>
int main(int argc, char** argv){
  int a,b;
  printf("a,b:");
  scanf("%d,%d",&a,&b);
  int sum=a+b, diff=a-b, div=a/b, mod=a%b;
  printf("sum=%d,diff=%d,div=%d,mod=%d\n",sum,diff,div,mod);
  return 0;
}
/pubx/build-llvm15/bin/clang-15  hello.c  -o hello.app
./hello.app
a,b:45,21
sum=66,diff=24,div=2,mod=3

加完花括号的llvm-project源码编译出的编译器clang-15 对 hello.c 实施编译, 编译出二进制文件 hello.app,

而该二进制文件 hello.app 正常运行

由此说明 ,花括号加的位置基本正确。

#统计

find /pubx/llvm-project/ -not -path '*/.git/*' -type f  \( -name "*.cpp" -or -name "*.c"  \)   | xargs -I% grep -Hn    BrcXxx    % > /pubx/BrcXxx.log

#把上一条bash命令抽成bash函数
findBrcCommentThenSave() {
  set -x #bash启用显示执行的命令
  keyword=$1
  find /pubx/llvm-project/ -not -path '*/.git/*' -type f \( -name "*.cpp" -or -name "*.c" \) | xargs -I% grep -Hn "$keyword" % |tee  /pubx/"${keyword}.log"
  set +x #bash禁止显示执行的命令
}
findBrcCommentThenSave BrcThen
findBrcCommentThenSave BrcSw
findBrcCommentThenSave BrcElse
findBrcCommentThenSave BrcFor
findBrcCommentThenSave BrcForRange
findBrcCommentThenSave BrcWhl
findBrcCommentThenSave BrcSw

各种语句分别加了多少花括号

ls -S /pubx/Brc* | xargs -I% sh -c  'wc -l %; ' 

'''
93201 /pubx/BrcThen.log
29832 /pubx/BrcSw.log
5539 /pubx/BrcElse.log
3603 /pubx/BrcFor.log
2187 /pubx/BrcForRange.log
663 /pubx/BrcWhl.log
'''

各种语句加了花括号的,有多少含有return

这些单语句return,由于没有被花括号包裹,才没有被t_clock_tick插入栈变量释放语句。
而tick插件栈变量分配、释放不平衡,具体为 栈变量共24万、最终残留2万没释放。 此不平衡是 由于 这些大约5万个单return语句没释放栈变量 导致的吗?
如下所示,被BrcPlugin插入花括号的语句中 大约5万个含有return.

ls -S /pubx/Brc* | xargs -I% sh -c  'echo -n "%    "; grep return % |wc -l '

'''
/pubx/BrcThen.log    50438
/pubx/BrcSw.log    2681
/pubx/BrcElse.log    815
/pubx/BrcFor.log    6
/pubx/BrcForRange.log    4
/pubx/BrcWhl.log    2
'''

实现

代码仓库(私有仓库) clang-plugin-add-brace.git

CMakeLists.txt

cmake_minimum_required(VERSION 3.13.4)

set(LIBFMT_DIR "/pubx/fmt/")
#set(LIBFMT_STATIC /pubx/fmt/include)
set(LIBFMT_INCLUDE "${LIBFMT_DIR}/include/")
#set(LIBFMT_STATIC /pubx/fmt/build/libfmt.a)
set(LIBFMT_STATIC "${LIBFMT_DIR}/build/libfmt.a")

include_directories( "${CMAKE_CURRENT_SOURCE_DIR}/include")
include_directories( "${CMAKE_CURRENT_SOURCE_DIR}/base_home/include/")

if (NOT EXISTS "${LIBFMT_STATIC}")
  MESSAGE(FATAL_ERROR "libfmt静态库${LIBFMT_STATIC} 不存在,请参照 build-libfmt.sh 构建libfmt静态库")
endif()

if (NOT EXISTS "${LIBFMT_INCLUDE}")
  MESSAGE(FATAL_ERROR "libfmt头文件目录${LIBFMT_INCLUDE} 不存在,请参照 build-libfmt.sh 构建libfmt静态库")
endif()

#===============================================================================
# 0. GET CLANG INSTALLATION DIR
#修改默认编译器
set(CT_Clang_INSTALL_DIR "/llvm_release_home/clang+llvm-15.0.0-x86_64-linux-gnu-rhel-8.4")
set(CMAKE_VERBOSE_MAKEFILE ON)
set(CURSES_LIBRARY "/lib64/libncurses.so.6")
set(CURSES_INCLUDE_PATH "/usr/include/")
set(CMAKE_EXPORT_COMPILE_COMMANDS True)
#编译器还是使用自带的gcc, 否则 调试时 没有 libstdc++  的调试信息,导致std::string在gdb中不显示,参考:https://stackoverflow.com/questions/58356385/python-exception-class-gdb-error-there-is-no-member-named-m-dataplus-whe/58356946#58356946
#   gdb显示std::string时报错: There is no member named _M_dataplus。 因此gdb不显示std::string的值.
#set(CMAKE_C_COMPILER "/llvm_release_home/clang+llvm-15.0.0-x86_64-linux-gnu-rhel-8.4/bin/clang")
#set(CMAKE_CXX_COMPILER "/llvm_release_home/clang+llvm-15.0.0-x86_64-linux-gnu-rhel-8.4/bin/clang++")
set(LLVM_DIR "/llvm_release_home/clang+llvm-15.0.0-x86_64-linux-gnu-rhel-8.4")
#set(xxx "")

project(clang-brc)
#project放到默认编译器定义之后,否则cmake会死循环


set(CT_LLVM_INCLUDE_DIR "${CT_Clang_INSTALL_DIR}/include/llvm")

set(CT_LLVM_CMAKE_FILE "${CT_Clang_INSTALL_DIR}/lib/cmake/clang/ClangConfig.cmake")

# http://llvm.org/docs/CMake.html#embedding-llvm-in-your-project
list(APPEND CMAKE_PREFIX_PATH "${CT_Clang_INSTALL_DIR}/lib/cmake/clang/")

find_package(Clang REQUIRED CONFIG)

# Sanity check. As Clang does not expose e.g. `CLANG_VERSION_MAJOR` through
# AddClang.cmake, we have to use LLVM_VERSION_MAJOR instead.
# TODO: Revisit when next version is released.
if(NOT "15" VERSION_EQUAL "${LLVM_VERSION_MAJOR}")
  message(FATAL_ERROR "Found LLVM ${LLVM_VERSION_MAJOR}, but need LLVM 15")
endif()

message(STATUS "Found Clang ${LLVM_PACKAGE_VERSION}")
message(STATUS "Using ClangConfig.cmake in: ${CT_Clang_INSTALL_DIR}")

message("CLANG STATUS:
  Includes (clang)    ${CLANG_INCLUDE_DIRS}
  Includes (llvm)     ${LLVM_INCLUDE_DIRS}"
)

# Set the LLVM and Clang header and library paths
include_directories(SYSTEM "${LLVM_INCLUDE_DIRS};${CLANG_INCLUDE_DIRS}")

#===============================================================================
# 3. CLANG-brc BUILD CONFIGURATION
#===============================================================================
# Use the same C++ standard as LLVM does
set(CMAKE_CXX_STANDARD 17 CACHE STRING "")

# Build type
if(NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE Debug CACHE
      STRING "Build type (default Debug):" FORCE)
endif()

# Compiler flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall\
    -fdiagnostics-color=always")

# LLVM/Clang is normally built without RTTI. Be consistent with that.
if(NOT LLVM_ENABLE_RTTI)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti")
endif()

# -fvisibility-inlines-hidden is set when building LLVM and on Darwin warnings
# are triggered if llvm-tutor is built without this flag (though otherwise it
# builds fine). For consistency, add it here too.
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag("-fvisibility-inlines-hidden"
  SUPPORTS_FVISIBILITY_INLINES_HIDDEN_FLAG)
if(${SUPPORTS_FVISIBILITY_INLINES_HIDDEN_FLAG} EQUAL "1")
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility-inlines-hidden")
endif()

# Set the build directories
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib")

add_subdirectory(BrcPlugin)
add_subdirectory(BrcAlone)
add_subdirectory(TestAlone)
add_subdirectory(test_in)

BrcMain.cpp

//BrcMain.cpp 
#include <clang/Frontend/FrontendActions.h>

#include "clang/Frontend/CompilerInstance.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.h"
#include "clang/Tooling/ArgumentsAdjusters.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "clang/Lex/PreprocessorOptions.h"
#include "Brc/BrcAstCnsm.h"
#include "base/ActMain.h"

using namespace llvm;
using namespace clang;

static llvm::cl::OptionCategory BrcAloneCategory("BrcAlone选项");

class _BrcAstAct : public ASTFrontendAction {
public:
    std::unique_ptr<ASTConsumer>
    CreateASTConsumer(CompilerInstance &CI,
                      llvm::StringRef inFile) override {
      SourceManager &SM = CI.getSourceManager();
      LangOptions &langOptions = CI.getLangOpts();
      ASTContext &astContext = CI.getASTContext();
      //Rewriter:2:  Rewriter构造完,在Action.CreateASTConsumer方法中 调用mRewriter.setSourceMgr后即可正常使用
      CI.getDiagnostics().setSourceManager(&SM);
      mRewriter_ptr->setSourceMgr(SM, langOptions);//A

      //Act中 是 每次都是 新创建 AddBraceAstCnsm
      return std::make_unique<BrcAstCnsm>(CI,mRewriter_ptr, &astContext, SM, langOptions);
    }
private:
    const std::shared_ptr<Rewriter> mRewriter_ptr=std::make_shared<Rewriter>();//这里是插件Act中的Rewriter,是源头,理应构造Rewriter.
};



int main(int Argc, const char **Argv) {
  const std::unique_ptr<tooling::FrontendActionFactory> &frontendActionFactory = clang::tooling::newFrontendActionFactory<_BrcAstAct>();
  int Result =   act_main(Argc,Argv,BrcAloneCategory,frontendActionFactory,"加花括号插件", false);
  return Result;
}


BrcAstAct.cpp

//BrcAstAct.cpp
#include "Brc/BrcAstCnsm.h"

#include "clang/AST/AST.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendPluginRegistry.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "Brc/CollectIncMacro_PPCb.h"

using namespace llvm;
using namespace clang;

class BrcAstAct : public PluginASTAction {
public:
    std::unique_ptr<ASTConsumer>
    CreateASTConsumer(CompilerInstance &CI,
                      llvm::StringRef inFile) override {
      SourceManager &SM = CI.getSourceManager();
      LangOptions &langOptions = CI.getLangOpts();
      Preprocessor &PP = CI.getPreprocessor();
      ASTContext &astContext = CI.getASTContext();
      CI.getDiagnostics().setSourceManager(&SM);
      mRewriter_ptr->setSourceMgr(SM, langOptions);

      // Act中 添加 收集#include、#define的 预处理回调
      PP.addPPCallbacks(std::make_unique<CollectIncMacro_PPCb>(CI));

      return std::make_unique<BrcAstCnsm>(CI,mRewriter_ptr, &astContext, SM, langOptions);
    }

    bool ParseArgs(const CompilerInstance &CI,
                   const std::vector<std::string> &Args) override {
      return true;
    }


    PluginASTAction::ActionType getActionType() override {
      //本插件自动运行:  在MainAction后运行本插件
      return AddAfterMainAction;
    }

//    void EndSourceFileAction() override { }  //   貌似有时候并没有调用EndSourceFileAction,因此去掉

private:
    const std::shared_ptr<Rewriter> mRewriter_ptr=std::make_shared<Rewriter>();//这里是插件Act中的Rewriter,是源头,理应构造Rewriter.
};

static FrontendPluginRegistry::Add<BrcAstAct>   actRegistry(/*Name=*/"BrcPlugin",  /*Description=*/"加花括号插件");

BrcVst.cpp

//BrcVst.cpp
#include "Brc/BrcVst.h"

#include "clang/AST/AST.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendPluginRegistry.h"
#include "base/Util.h"
#include "Brc/RangeHasMacroAstVst.h"
#include "Brc/CollectIncMacro_PPCb.h"

using namespace clang;

#include <iostream>
#include <clang/AST/ParentMapContext.h>
#include <fmt/core.h>


using namespace llvm;
using namespace clang;



void BrcVst::letLRBraceWrapRangeAftBf(SourceLocation B, SourceLocation E, const char* whoInserted ){

  //region 如果被包裹语句 处在宏中 则不处理 直接返回。
  if(
    Util::LocIsInMacro(B,SM)
    ||
    Util::LocIsInMacro(E,SM)
  ){
    return;
  }
  //endregion

  //region 跳过非MainFile. 场景: '#include "xxx.def"', 跳过xxx.def, 即 不修改xxx.def
  if( !Util::LocFileIDEqMainFileID(SM, B) || !Util::LocFileIDEqMainFileID(SM, E) ){
//    Util::printStmt(CI,"查看","暂时不对间接文件插入时钟语句",stmt, true); //开发用打印
    return  ;
  }
  //endregion


  //region 获取主文件ID,文件路径
  FileID mainFileId;
  std::string filePath;
  Util::getMainFileIDMainFilePath(SM,mainFileId,filePath);
  //endregion

  //region 若此位置已经插入过左花括号, 则不再插入,防止重复
  LocId LBraceLocId=LocId::buildFor(filePath, B, SM);
  if(Util::LocIdSetContains(LBraceLocIdSet,LBraceLocId)){
    return ;
  }
  //endregion

  //region 插入左右花括号

  //构造人类可读开始位置、结束位置、插入者 注释文本
  std::string hmTxtCmnt_whoInsrt_BE;
  Util::BE_Loc_HumanText(SM, B, E, whoInserted, hmTxtCmnt_whoInsrt_BE);

  mRewriter_ptr->InsertTextAfterToken(B,"{");

  std::string RBraceStr("}"+hmTxtCmnt_whoInsrt_BE);

  //记录已插入左花括号的节点ID们以防重: 即使重复遍历了 但不会重复插入
  LBraceLocIdSet.insert(LBraceLocId);
  
  mRewriter_ptr->InsertTextBefore(E,RBraceStr);
  //endregion
}

/**
 * 用左右花括号包裹给定语句
 * @param stmt
 * @param whoInserted
 * @return
 */
void BrcVst::letLRBraceWrapStmtBfAfTk(Stmt *stmt, const char* whoInserted){
  SourceLocation beginLoc = stmt->getBeginLoc();
  SourceLocation endLoc = stmt->getEndLoc();

  //region 如果被包裹语句 处在宏中 则不处理 直接返回。

  if(
    Util::LocIsInMacro(beginLoc,SM)
    ||
    Util::LocIsInMacro(endLoc,SM)
  ){
    return;
  }
  //endregion

  //region 跳过非MainFile. 场景: '#include "xxx.def"', 跳过xxx.def, 即 不修改xxx.def
  if( !Util::LocFileIDEqMainFileID(SM, beginLoc) ){
//    Util::printStmt(CI,"查看","暂时不对间接文件插入时钟语句",stmt, true); //开发用打印
    return  ;
  }
  //endregion


  //region 获取主文件ID,文件路径
  FileID mainFileId;
  std::string filePath;
  Util::getMainFileIDMainFilePath(SM,mainFileId,filePath);
  //endregion

  //region 若此位置已经插入过左花括号, 则不再插入,防止重复
  LocId LBraceLocId=LocId::buildFor(filePath, beginLoc, SM);
  if(Util::LocIdSetContains(LBraceLocIdSet,LBraceLocId)){
    return ;
  }
  //endregion

  //region 插入左右花括号

  //找结尾分号
  bool endIsSemicolon=false;
  SourceLocation endSemicolonLoc = Util::getStmtEndSemicolonLocation(stmt,SM,endIsSemicolon);

  //构造人类可读开始位置、结束位置、插入者 注释文本
  std::string hmTxtCmnt_whoInsrt_BE;
  Util::BE_Loc_HumanText(SM, beginLoc, endSemicolonLoc, whoInserted, hmTxtCmnt_whoInsrt_BE);

  if(endIsSemicolon){
    //只有找到分号位置,才可以插入左右花括号。
    //   不能造成插入了左花括号,却没找到分号,然后没法插入右花括号,也没法撤销左花括号,而陷入语法错误。
    mRewriter_ptr->InsertTextBefore(beginLoc,"{");

    std::string RBraceStr("}"+hmTxtCmnt_whoInsrt_BE);

    mRewriter_ptr->InsertTextAfterToken(endSemicolonLoc,RBraceStr);

    //记录已插入左花括号的节点ID们以防重: 即使重复遍历了 但不会重复插入
    LBraceLocIdSet.insert(LBraceLocId);
  }
  //endregion

}



bool BrcVst::TraverseIfStmt(IfStmt *ifStmt){
  //region 若NULL,直接返回
  if(!ifStmt){
    return false;
  }
  //endregion

  //region 跳过非MainFile
  if( !Util::LocFileIDEqMainFileID(SM, ifStmt->getBeginLoc()) ){
//    Util::printStmt(CI,"查看","暂时不对间接文件插入时钟语句",stmt, true); //开发用打印
    return false;
  }
  //endregion
  
  //region 自定义处理: if的then语句、else语句 若非块语句 则用花括号包裹

  Stmt *thenStmt = ifStmt->getThen();
  if(thenStmt && !Util::isAloneContainerStmt(thenStmt) )  {
    letLRBraceWrapStmtBfAfTk(thenStmt, "BrcThen");
  }

  Stmt *elseStmt = ifStmt->getElse();
  if(elseStmt && !Util::isAloneContainerStmt(elseStmt) ) {
    letLRBraceWrapStmtBfAfTk(elseStmt, "BrcElse");
  }
//endregion 自定义处理 完毕

  //region  将递归链条正确的接好:  对 当前节点ifStmt的下一层节点child:{then,else}  调用顶层方法TraverseStmt(child)

  if(thenStmt){
      TraverseStmt  (thenStmt);
  }

  if(elseStmt){
    TraverseStmt(elseStmt);
  }
  //endregion


  //继续遍历剩余源码
  //  TraverseXxxStmt末尾返回true  表示继续遍历剩余源码
  //  TraverseXxxStmt末尾返回false 表示从此结束遍历,遍历剩余不再遍历
  return true;
}
bool BrcVst::TraverseWhileStmt(WhileStmt *whileStmt){
  //region 若NULL,直接返回
  if(!whileStmt){
    return false;
  }
  //endregion

  //region 跳过非MainFile
  if( !Util::LocFileIDEqMainFileID(SM, whileStmt->getBeginLoc()) ){
//    Util::printStmt(CI,"查看","暂时不对间接文件插入时钟语句",stmt, true); //开发用打印
    return false;
  }
  //endregion

  //region 自定义处理: while的循环体语句 若非块语句 则用花括号包裹
  Stmt *bodyStmt = whileStmt->getBody();
  if(bodyStmt && !Util::isAloneContainerStmt(bodyStmt) )  {
    letLRBraceWrapStmtBfAfTk(bodyStmt, "BrcWhl");
  }

  //endregion 自定义处理 完毕

  //region  将递归链条正确的接好:  对 当前节点whileStmt的下一层节点child:{body} 调用顶层方法TraverseStmt(child)
  if(bodyStmt){
    Stmt::StmtClass bodyStmtClass = bodyStmt->getStmtClass();
//    if(bodyStmtClass==Stmt::StmtClass::CompoundStmtClass){
//是否向下层遍历 与 本节点whileStmt的直接子结点 是否为 块语句 无关,因为 whileStmt的深层子结点 可能是块语句
//	即使whileStmt的直接子节点不是块语句 但不影响 whileStmt的深层子结点 可能是块语句
      TraverseStmt(bodyStmt);
//    }
  }
  //endregion

  //继续遍历剩余源码
//  TraverseXxxStmt末尾返回true  表示继续遍历剩余源码
//  TraverseXxxStmt末尾返回false 表示从此结束遍历,遍历剩余不再遍历
  return true;
}

//forEach和for很相似
bool BrcVst::TraverseForStmt(ForStmt *forStmt) {
  //region 若NULL,直接返回
  if(!forStmt){
    return false;
  }
  //endregion

  //region 跳过非MainFile
  if( !Util::LocFileIDEqMainFileID(SM, forStmt->getBeginLoc()) ){
//    Util::printStmt(CI,"查看","暂时不对间接文件插入时钟语句",stmt, true); //开发用打印
    return false;
  }
  //endregion

  //region 自定义处理: for的循环体语句 若非块语句 则用花括号包裹
  Stmt *bodyStmt = forStmt->getBody();
  if(bodyStmt && !Util::isAloneContainerStmt(bodyStmt) )  {
    letLRBraceWrapStmtBfAfTk(bodyStmt, "BrcFor");
  }
  //endregion

  //region  将递归链条正确的接好:  对 当前节点forStmt的下一层节点child:{body} 调用顶层方法TraverseStmt(child)
  if(bodyStmt){
    Stmt::StmtClass bodyStmtClass = bodyStmt->getStmtClass();
//    if(bodyStmtClass==Stmt::StmtClass::CompoundStmtClass){
//是否向下层遍历 与 本节点forStmt的直接子结点 是否为 块语句 无关,因为 forStmt的深层子结点 可能是块语句
//	即使forStmt的直接子节点不是块语句 但不影响 forStmt的深层子结点 可能是块语句
      TraverseStmt(bodyStmt);
//    }
  }
  //endregion

  //继续遍历剩余源码
  //  TraverseXxxStmt末尾返回true  表示继续遍历剩余源码
  //  TraverseXxxStmt末尾返回false 表示从此结束遍历,遍历剩余不再遍历
  return false;
}

//forEach和for很相似
bool BrcVst::TraverseCXXForRangeStmt(CXXForRangeStmt *forRangeStmt) {
  //region 若NULL,直接返回
  if(!forRangeStmt){
    return false;
  }
  //endregion

  //region 跳过非MainFile
  if( !Util::LocFileIDEqMainFileID(SM, forRangeStmt->getBeginLoc()) ){
//    Util::printStmt(CI,"查看","暂时不对间接文件插入时钟语句",stmt, true); //开发用打印
    return false;
  }
  //endregion

  //region 自定义处理: for的循环体语句 若非块语句 则用花括号包裹
  Stmt *bodyStmt = forRangeStmt->getBody();
  if(bodyStmt && !Util::isAloneContainerStmt(bodyStmt) )  {
    letLRBraceWrapStmtBfAfTk(bodyStmt, "BrcForRange");
  }
  //endregion

  //region  将递归链条正确的接好:  对 当前节点forStmt的下一层节点child:{body} 调用顶层方法TraverseStmt(child)
  if(bodyStmt){
    Stmt::StmtClass bodyStmtClass = bodyStmt->getStmtClass();
//    if(bodyStmtClass==Stmt::StmtClass::CompoundStmtClass){
//是否向下层遍历 与 本节点forStmt的直接子结点 是否为 块语句 无关,因为 forStmt的深层子结点 可能是块语句
//	即使forStmt的直接子节点不是块语句 但不影响 forStmt的深层子结点 可能是块语句
    TraverseStmt(bodyStmt);
//    }
  }
  //endregion

  //继续遍历剩余源码
  //  TraverseXxxStmt末尾返回true  表示继续遍历剩余源码
  //  TraverseXxxStmt末尾返回false 表示从此结束遍历,遍历剩余不再遍历
  return false;
}

bool BrcVst::TraverseSwitchStmt(SwitchStmt *switchStmt){
  
  //region 跳过非MainFile
  if( !Util::LocFileIDEqMainFileID(SM, switchStmt->getBeginLoc()) ){
//    Util::printStmt(CI,"查看","暂时不对间接文件插入时钟语句",stmt, true); //开发用打印
    return false;
  }
  //endregion

  SwitchCase *caseList = switchStmt->getSwitchCaseList();
  LangOptions &LO = CI.getLangOpts();

  std::vector<SwitchCase*> caseVec;
  //这里假定了: SwitchCase::getNextSwitchCase 获取的顺序 目前 看是  书写case的次序的逆序,
  //    但不能排除 出现  乱须 毫无规律,所以 排序 才靠谱
  for (SwitchCase* switchCase = caseList; switchCase; switchCase = switchCase->getNextSwitchCase()) {
    caseVec.push_back(switchCase);
  }

  //对各个case语句 按开始位置 升序 排序
  std::sort(caseVec.begin(), caseVec.end(), [](clang::SwitchCase* lhs, clang::SwitchCase* rhs) {
      return lhs->getBeginLoc() < rhs->getBeginLoc();
  });

  SourceLocation beginLoc;
  SourceLocation endLoc;
  size_t caseCnt = caseVec.size();
  for(int k=0; k < caseCnt; k++){
    SwitchCase* scK=caseVec[k];

    Stmt *subStmt = scK->getSubStmt();
//    Util::printStmt(*Ctx,CI,"sub","",subStmt,true);
//    Util::printStmt(*Ctx,CI,"scK","",scK,true);
    bool subStmtIsCompound = isa<CompoundStmt>(*subStmt);

    //开始位置为冒号的下一个Token所在位置
    //    注意此方法中的代码 是否在任何情况下都能实现 移动到下一个位置 有待确定
    beginLoc = scK->getColonLoc();




    if(k<caseCnt-1){
      endLoc=caseVec[k+1]->getBeginLoc();
    }else{
      endLoc=switchStmt->getEndLoc();
    }
    if ( isa<CaseStmt>(*scK)) {
      CaseStmt *caseK = dyn_cast<CaseStmt>(scK);


      //region 输出case 后表达式 , 开发用
//      Expr *LHS = caseK->getLHS();
//      LangOptions &LO = CI.getLangOpts();
//      Token tk;
//      Lexer::getRawToken(LHS->getExprLoc(),tk,SM,LO,true);
//      bool invalid;
//      const std::string &tkStr = Lexer::getSpelling(tk, SM, LO, &invalid);
//
//      llvm::outs() << "case " << tkStr << ":";
      //endregion

    }else if ( isa<DefaultStmt>(*scK)) {
      DefaultStmt *defaultK = dyn_cast<DefaultStmt>(scK);

      //region 输出default , 开发用
//      llvm::outs() << "default "  << ":";
      //endregion
    }


    //region 开发用输出
//    llvm::outs() << ",是否块:"<< std::to_string(subStmtIsCompound) <<",case开始: " << beginLoc.printToString(SM)
//    << ", case结束: " << endLoc.printToString(SM) << "\n";
    //endregion

    //如果case体不是块,才用花括号包裹.
    if(!subStmtIsCompound){

      //region 用一Visitor遍历该switch中每个语句 且 限制范围为 case内,记录 case内 是否有宏、子语句个数
      //   理论上可以写成 用一Visitor遍历该case中每个语句 且 限制范围为 case内,但这又回到之前的老问题 即 拿不到case的完整子语句们。 但能拿到完整switch啊,所以才有上面遍历办法。
      // case内 即 case冒号后到下一case前内语句,
      //   对比:
      //        Traverse、Visitor 总是能触及到 case内的每条语句的
      //        case.children、case.getSubStmt只能触及到 case内的前部分语句
      SourceRange BE=SourceRange(beginLoc, endLoc);
      RangeHasMacroAstVst rv(CI,BE);
      rv.TraverseStmt(switchStmt);
    //endregion

      //region 如果此case内有宏 或 该case内无语句 或 该case内有#include 或 该case内有#define,则不处理。
      // 如果此case内有宏 或 该case冒号到下'case'前无语句,则不处理。 否则 此case内无宏,则处理
      if(
      //如果此case内有宏,则不处理
      rv.hasMacro ||
      //如果此case内无子语句,则不处理
      rv.caseKSubStmtCnt==0 ||
      //如果此case有 直接写在'case'内的变量声明,则不处理。
      //   理由是,caseK中直接声明的变量可能在caseJ中使用,若caseK被花括号包裹,则caseJ无法使用该变量。
      rv.VarDeclDirectlyInCaseKCnt > 0 ||
      //预处理回调已经收集了#include、#define ,这里判断case起止范围内 有无#i、#d,若有 则不处理该case
      CollectIncMacro_PPCb::hasInclusionDirective(SM, BE) || CollectIncMacro_PPCb::hasMacroDefined(SM, BE)
      ){
        //如果此case内有宏,则不处理
        continue;
      }
      //endregion

      //region 开发用
//      std::string msg;
//      int line=-1,col=-1;
//      Util::extractLineAndColumn(SM,scK->getBeginLoc(),line,col);
//      msg=fmt::format("{},line={}...col={},",msg,line,col);
//      Util::printStmt(*Ctx,CI,"scK",msg,scK,true);//开发用
      //endregion

      //region 否则  处理 此case
      // 否则 此case内 无宏、且有语句,则处理
      letLRBraceWrapRangeAftBf(beginLoc, endLoc, "BrcSw");

      //endregion

    }
  }


  /**输出
case 0:,是否块:1,case开始: /pubx/clang-brc/test_in/test_main.cpp:23:14, case结束: /pubx/clang-brc/test_in/test_main.cpp:30:7
case 1:,是否块:0,case开始: /pubx/clang-brc/test_in/test_main.cpp:30:14, case结束: /pubx/clang-brc/test_in/test_main.cpp:34:7
case 2:,是否块:0,case开始: /pubx/clang-brc/test_in/test_main.cpp:34:14, case结束: /pubx/clang-brc/test_in/test_main.cpp:38:7
case 3:,是否块:0,case开始: /pubx/clang-brc/test_in/test_main.cpp:38:14, case结束: /pubx/clang-brc/test_in/test_main.cpp:40:7
case 4:,是否块:1,case开始: /pubx/clang-brc/test_in/test_main.cpp:40:14, case结束: /pubx/clang-brc/test_in/test_main.cpp:47:7
case 6:,是否块:1,case开始: /pubx/clang-brc/test_in/test_main.cpp:47:14, case结束: /pubx/clang-brc/test_in/test_main.cpp:52:7
case 7:,是否块:1,case开始: /pubx/clang-brc/test_in/test_main.cpp:52:14, case结束: /pubx/clang-brc/test_in/test_main.cpp:56:7
default :,是否块:0,case开始: /pubx/clang-brc/test_in/test_main.cpp:56:15, case结束: /pubx/clang-brc/test_in/test_main.cpp:59:5


case语句列表 按 开始位置 升序 排序
当前case结束位置 用下一个case开始位置表示, 最后一个case结束位置 用整个switch的结束位置表示
当前case开始位置 用case或default后的冒号的位置
SwitchCase::getEndLoc 表达的 case结尾位置 基本都不对, case1的结尾只能用case2的开始来表达了。
   */


  //region  将递归链条正确的粘接好:  对 当前节点switchStmt的下一层节点child:{body}  调用顶层方法TraverseStmt(child)
  for(int k=0; k < caseCnt; k++) {
    SwitchCase *caseK = caseVec[k];
// 由于 case的子语句 是伪命题,
//  即 case的子语句 理论上 包括两部分 :
//    部分1: caseK.getSubStmt() 、
//    部分2: 书写在caseK下但直接属于switch的语句
// 导致 遍历caseK会漏掉 部分2,
//    TraverseStmt(caseK);
// 所以 为了不遗漏,得要遍历 switch体,
//   注意 不要遍历switch, 否则 可能死循环,详细原因如下:
//     当前方法 即TraverseSwitchStmt(switchJ)  , 内 再出现TraverseSwitchStmt(switchJ) , 显然是无条件环 即 死循环。
  }
  //遍历 switch体
  TraverseStmt(switchStmt->getBody());
  //endregion


  //继续遍历剩余源码
  //  TraverseXxxStmt末尾返回true  表示继续遍历剩余源码
  //  TraverseXxxStmt末尾返回false 表示从此结束遍历,遍历剩余不再遍历
  return true;
}




RangeHasMacroAstVst.cpp

// RangeHasMacroAstVst.cpp
#include "Brc/RangeHasMacroAstVst.h"



bool RangeHasMacroAstVst::VisitStmt(clang::Stmt *stmt) {

  //region 当-I头文件路径没有设置时,VisitStmt(stmt) 中语句的位置范围可能是不合法的
  SourceRange stmtR = stmt->getSourceRange();
  bool valid=stmtR.isValid();
  if(!valid){
    return true;
  }
  //endregion

  SourceManager &SM = CI.getSourceManager();
  ASTContext &Ctx = CI.getASTContext();


  //region 若 当前stmt 是 caseK的子语句, 则累加子语句个数, 否则直接返回。若是子语句且是变量声明语句,则累加变量声明语句个数。
  // 通过 起至范围 包含,判定  当前语句stmt 是否 caseK的子语句
  // 如果遍历到的语句stmt的起至范围 不完全 含在 caseK起至范围内 , 即不是 caseK的子语句 ,则 不处理,直接返回。
  // 这种情况可能是拿到了一个更大的非终结符号。
  //注意: SourceRange::fullyContains 结果是错误的, 才有自制方法Util::fullContains

  bool inCaseKRange =  Util::fullContains(SM, caseKSrcRange, stmtR);
  if(inCaseKRange) {
    //若 当前stmt 是 caseK的子语句
    //此if块内代码,是针对caseK中每个子语句都会执行的。

    //若 当前stmt 是 caseK的子语句, 则累加子语句个数
    caseKSubStmtCnt++;

    //若当前语句是直接写在'case'内的变量声明   ,则累其语句个数
    if (stmt->getStmtClass() == Stmt::StmtClass::DeclStmtClass) {
      DynTypedNode parent;  ASTNodeKind parentNK;
      DynTypedNode pParent;  ASTNodeKind pParentNK;
      bool only1P= Util::only1ParentNodeKind(CI, Ctx, stmt, parent, parentNK);
      bool parentNKIsCompound=ASTNodeKind::getFromNodeKind<CompoundStmt>().isSame(parentNK);
      bool belongSwitchDirectlyInCaseK=
          //直接写在'case'内, 但是其父亲是switch块的
          only1P
          && parentNKIsCompound//父是'switch {}'中的 '{}'
           && Util::only1ParentNodeKind(CI, Ctx, parent.get<Stmt>(), pParent, pParentNK)
          && ASTNodeKind::getFromNodeKind<SwitchStmt>().isSame(pParentNK)    //父父是 'switch'
           ;
      bool normalDirectlyInCase=
          //直接写在'case'内,其父亲是case语句的
          only1P && parentNKIsCompound;
      if(
          //直接写在'case'内, 但是其父亲是switch块的
          belongSwitchDirectlyInCaseK
          ||
          //直接写在'case'内,其父亲是case语句的
          normalDirectlyInCase
//注意: 直接写的'case {'内的 、 直接写在'case'内  不是一回事,而是差别很大。
      ){
        VarDeclDirectlyInCaseKCnt++;
  Util::printStmt(CI.getASTContext(),CI,"直接写在'case'内","VarDeclDirectlyInCaseKCnt",stmt,true);
      }



    }
  }else{
    //若 当前stmt 不是 caseK的子语句, 则直接返回。
    return true;
  }
  //endregion


//  Util::printStmt(CI.getASTContext(),CI,"caseK的子语句","",stmt,true);

  //到此处, 语句stmt 一定是 caseK的子语句


  //region 若 已经标记 caseK子语句中,则直接返回,且不需要再遍历了。
  // 如果已经确定 给定Switch的限定位置范围内 有宏,则直接返回,且不需要再遍历了
  if(hasMacro){
    //若 已经标记 caseK子语句中,则直接返回,且不需要再遍历了。
    //    返回false 表示 告知上层Traverse*方法 不需要再遍历了?
    return false;
  }
  //endregion


  //region 如果遍历到块语句,不处理,直接返回。因为这里只关心单语句,不关心块语句。
  if (clang::isa<clang::CompoundStmt>(stmt)) {
    return true;
  }
  //endregion

  //到此处 遍历到的语句 一定是 单语句,即 不是 块语句

  //region 若此单语句 在宏中,则 标记 caseK子语句中 有宏,并 直接返回 且不需要再遍历了。
  // 如果遍历到的单语句,开始位置在宏中 或 结束位置在宏中,则 给定Switch的限定位置范围内 有宏,直接返回,且不需要再遍历了。
  SourceLocation B = stmt->getBeginLoc();
  SourceLocation E = stmt->getEndLoc();
  bool inMacro = Util::LocIsInMacro(B,SM) || Util::LocIsInMacro(E,SM);
  if(!hasMacro ){
    if(inMacro){
      //若此单语句 在宏中, 则 标记 caseK子语句中 有宏
      hasMacro=true;

      //并 直接返回 且不需要再遍历了
      return false;
    }
  }
  //endregion

  //其他情况,继续遍历
  return true;
}

//CollectIncMacro_PPCb.cpp: 略
//BrcAstCnsm.cpp: 略
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ziix

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值