前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ASTMatcher分析函数调用链(下)

ASTMatcher分析函数调用链(下)

原创
作者头像
adding
修改2019-10-21 07:33:13
2.3K1
修改2019-10-21 07:33:13
举报
文章被收录于专栏:ASTMatcherASTMatcher

上一篇文章(ASTMatcher分析函数调用链(上))讲到ASTMatcher的原理以及创建,本文将详细介绍ASTMatcher获取函数调用链在iOS app中的应用。

一、ASTMatcher部分

1、无消息调用的函数定义获取

上篇中的ASTMatcher只能获取有消息调用的函数定义,那没有消息调用的函数定义就无法匹配到,所以无消息调用的函数定义也需要获取

DeclarationMatcher AllFuncMatcher = objcMethodDecl(
            hasAncestor(objcImplementationDecl().bind("allClass"))
        ).bind("allSelector");

同理也需要获取匹配节点

2、category类的消息调用获取

objcImplementationDecl()中并不包括category类,所以category类需要单独写Matcher匹配

DeclarationMatcher CategoryFuncLinkMatcher = objcMethodDecl(
    hasAncestor(objcCategoryImplDecl().bind("myCategoryClass"))
    ,forEachDescendant(objcMessageExpr().bind("categoryFuncCaller"))
        ).bind("myCategorySelector");

同理也需要获取匹配节点

3、缓存文件目录

本文将分析到的消息调用、函数定义和类声明写到缓存文件中,这里需要适配多业务,缓存目录不能写死在同一个文件夹,所以缓存目录通过参数传递:

    rootPath = argv[1];
    std::vector<std::string> sv;
    split(rootPath, sv, '/');
    for (const auto& s : sv) {
        fileName = s;
    }

Matcher命令行中必须通过-extra-arg参数传递:

~/clang-llvm/build/bin/func-call ~/www/CYHTest/get_func_link/demoB.m -extra-arg=~/www/AST_Matcher_Result/func-category-new -- -I ~/www/CYHTest/get_func_link/

二、脚本部分

分析整个app的函数调用关系,需要遍历每个.m和.mm文件,并且每个.m和.mm文件引用的头文件目录必须 -I 给Matcher,所以需要拿到每个.m和.mm以及其对应的.h文件的引用文件路径集合。

1、.h文件与其所在目录的对应关系

# 获取头文件所在目录,为后续参数-I做准备
def get_head_file_relation(workspace):
    head_dir_relation = {}
    for root, dirs, files in os.walk(workspace):
        for file_one in files:
            if file_one.endswith(".h"):
                head_dir_relation[file_one] = root
    return head_dir_relation

2、.m和.mm文件引用头文件对应关系

# 获取每个.m或者.mm文件import的头文件所在目录,输出为字典
def get_import_file_dict(file_list, head_dir_relation):
    ...

3、屏蔽条件语句

ASTMatcher分析不到条件语句中的代码,所以条件语句全部屏蔽

def do_work(ROOT):
    line_records = {}
    for root, dirs, files in os.walk(ROOT):
        for file in files:
            file_name, file_extension = os.path.splitext(file)
            if file_extension in ['.m', '.mm']:
                path = os.path.join(root, file)
                line_records[path] = pre_edit(path)
    with open("line_record.txt", 'wb') as fp:
        json.dump(line_records, fp)

def pre_edit(path):
    line_record = []
    new_lines = []
    with open(path, 'rt') as fp:
        lines = fp.readlines()
        reg = re.compile(r'^\s*\#if|^\s*\#else|^\s*\#endif|^\s*\#error')
        for index, line in enumerate(lines):
            for m in reg.finditer(line):
                line = line[:m.start()] + '//' + line[m.start():]
                line_record.append(index)
                break
            new_lines.append(line)
    with open(path, 'wt') as fp:
        fp.writelines(new_lines)
    return line_record

4、分析对应app工程

获取所有.m和.mm文件list,循环通过func-call分析(这里使用func-call和func-call-category-only两个ASTMatcher,3.5章节有解释)

for file_one in file_list:
    try:
        header_one_file_str = " -I ".join(file_head_relation[file_one])
        terminal = "~/clang-llvm-6.0/build/bin/func-call {} -extra-arg={} -- -I {}".format(file_one, ast_matcher_cache_dir, header_one_file_str)
        status, output = commands.getstatusoutput(terminal)
        if status == 0:
            print 'success:', number
            number = number + 1
        else:
            print 'fail:', fail, ":", file_one
            fail = fail + 1
            fail_file_list.append(file_one)
    except Exception as e:
        except_number = except_number + 1
        except_file_list.append(file_one)
        print "except file:", e, ":", file_one
        continue

for file_one_category in file_list:
    try:
        header_one_file_str = " -I ".join(file_head_relation[file_one_category])
        terminal = "~/clang-llvm/build/bin/func-call-category-only {} -extra-arg={} -- -I {}".format(file_one_category, ast_matcher_cache_dir, header_one_file_str)
        status, output = commands.getstatusoutput(terminal)
        if status == 0:
            print 'success category:', number
            number = number + 1
        else:
            print 'fail category:', fail, ":", file_one_category
            fail = fail + 1
            fail_file_list.append(file_one_category)
    except Exception as e:
        except_number = except_number + 1
        except_file_list.append(file_one_category)
        print "except file category:", e, ":", file_one_category
        continue

5、分析结果以及DB

三、踩过的一些坑

1、无法获取objcCategoryImplDecl()

解决:原来clang6.0不支持获取objcCategoryImplDecl(),使用的clang版本比较旧,更新至最新clang8.0即可。

2、category无法获取类名,只能获取别名

解决:ObjCCategoryImplDecl()->getIdentifier()->getName()方法只能获取category类的别名,通过ObjCCategoryImplDecl()->getCategoryDecl()->getClassInterface()->getNameAsString()获取类名,曲线救国~

const ObjCCategoryImplDecl *categoryImplDecl = Result.Nodes.getNodeAs<ObjCCategoryImplDecl>("myCategoryClass");
std::string categoryName = categoryImplDecl->getIdentifier()->getName();
ObjCCategoryDecl *categoryDecl = categoryImplDecl->getCategoryDecl();
if(!categoryDecl->IsClassExtension())
{   
    //这里有两种情况,一种是Extension,一种是Category。这里是处理Category
    ObjCInterfaceDecl *itfcdecl = categoryDecl->getClassInterface();
    outCategoryMessage << itfcdecl->getNameAsString() << "::";
 }

3、CMake Error CMakeLists.txt:6 (target_link_libraries)

在clang更新到clang8.0后,sudo ninja构建时报错CMake Error at tools/clang/tools/extra/func-category/CMakeLists.txt:6 (target_link_libraries):

解决:CMakeLists.txt target_link_libraries中加入PRIVATE,见链接:https://stackoverflow.com/questions/47737558/build-llvm-clangtool

4、clang8.0生成的func-call获取调用链不全

clang8.0生成的func-call可执行文件获取调用链不全,具体原因没有去研究

解决:func-call使用clang6.0生成的可执行文件,func-call-category-only使用clang8.0生成的可执行文件。这里还要注意func-call-category-only依赖clang8.0的环境,所以机器上clang环境还得是clang8.0。

这就导致了普通函数的获取与category类函数的获取不能在同一个ASTMatcher中,所以需要两个ASTMatcher来分析整个工程,这里后续再优化~

5、categroy类函数通过分析all file得到

app category类函数的获取有两种方法,一是遍历整个工程目录,拿到文件名中有“+”号的category文件list,然后循环分析;二是分析整个app所有.m和.mm文件。方式一速度快,但不是以“+”号命名的category类无法获取;方式二会增加分析时间,但可以分析到所有category类,数据比较完整。

解决:本文为了数据完整性,牺牲分析时间,采用方式二。

四、ASTMatcher无法分析的情况

1、系统方法

由于系统方法在我们调用链中没有用处,所以这里就放弃了对系统方法的获取,包括系统类的category类以及方法。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、ASTMatcher部分
    • 1、无消息调用的函数定义获取
      • 2、category类的消息调用获取
        • 3、缓存文件目录
        • 二、脚本部分
          • 1、.h文件与其所在目录的对应关系
            • 2、.m和.mm文件引用头文件对应关系
              • 3、屏蔽条件语句
                • 4、分析对应app工程
                  • 5、分析结果以及DB
                  • 三、踩过的一些坑
                    • 1、无法获取objcCategoryImplDecl()
                      • 2、category无法获取类名,只能获取别名
                        • 3、CMake Error CMakeLists.txt:6 (target_link_libraries)
                          • 4、clang8.0生成的func-call获取调用链不全
                            • 5、categroy类函数通过分析all file得到
                            • 四、ASTMatcher无法分析的情况
                              • 1、系统方法
                              领券
                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档