赞
踩
若case内, 以下任一条成立,则 跳过该case 即 不会对该case内容用花括号包裹.
- 有#define、
- 有#include、
- 有直属变量声明、
- 空case、
- 有宏调用
CollectIncMacro_PPCb:Collect Inlucde Macro PPCallbacks : 收集Inlucde和Macro的预处理回调
收集 #include、 #define , 以判断case起止范围内 有无 #include、 #define
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
对llvm-project的每个源文件的编译过程应用插件libBrcPlugin.so 以 对 该源文件中单语句加花括号
source /pubx/llvm-project/doc_clang15_build/brc_build1_plugin.sh
source /pubx/llvm-project/doc_clang15_build/brc_build2_directly.sh
//编写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
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
#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
#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
#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
#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: 略
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。