一款方便、高效的基于tree-sitter的代码风格转换器,支持Python、C/C++和Java共100多种风格转换


源代码网址:https://github.com/rebibabo/SCTS/tree/main

如果有引用本文或者工具,请注明引用来源

如果觉得对您有帮助,还请各位帅哥美女在github点个免费的star🙏

工具介绍

实现了一款多语言代码等价语义转换器,基于tree-sitter开发工具包,对代码进行解析,并在具体语法树(concrete syntax tree, CST)树上进行节点的匹配和替换,支持Python、C/C++和Java共100多种风格转换,处理2000份代码仅需要13.6s。

代码风格转换器可以应用在以下的场景

  • 后门/对抗攻击:实现隐蔽性高的攻击
  • 数据集扩展:提高模型性能和鲁棒性,使模型更好的对抗扰动
  • 模型水印:向数据集的代码注入水印,保护模型或数据集的产权
  • 统一风格:增强代码的可读性
  • 代码混淆:防止反编译

现有代码风格转换器方法可以大致分为两类:
语法分析器

  • 优点:精确匹配语法规则,保证语义等价和语法正确,转换速度快,能够大批量处理数据集
  • 缺点:需要设计转换规则,比较费时

深度学习模型

  • 优点:不需要指定转换规则,只需要通过数据集训练模型即可,转换成功率高
  • 缺点:token长度限制,生成的代码语法可能错误,并且生成速度非常慢。

本工具收集整理了四种语言C,C++,Python和Java四种语言的112条语义等价变换,设计了增删算法,能够高效处理大批量代码,提升转换速率。

环境搭建

运行命令pip install -r requirements.txt安装python环境,如果想可视化CST树,请安装graphviz,CST树样例如下
在这里插入图片描述
linux请运行sudo apt-get install git graphviz graphviz-doc安装,windows系统请从官网上下载,并配置环境变量
如果不想可视化CST树,可以注释掉所有和graphviz相关的代码

使用教程

构建scts类,设置代码的语言种类

from SCTS.change_program_style import SCTS
scts = SCTS('c')

然后调用change_file_style方法,第一个参数为风格列表,第二个参数为代码文本,返回值第一个元素为风格转换后的代码,第二个元素为转换是否成功,判断依据为代码是否发生变化且语法正确。

new_code, succ = scts.change_file_style(["8.11", "0.1"], code)

例如可以将C语言的for循环修改风格,将初始化语句提到for循环前,条件语句移到循环体内部,自增语句放在循环体末尾。
在这里插入图片描述
其他的风格转换样例
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

如果想要可视化CST树,执行以下代码

scts.see_tree(code)

如果想将代码进行分词,返回分词列表,执行下面的代码

scts.tokenize(code)

工具框架流程图

工具框架如下图所示,初始化模块输入代码的语言,根据指定语言构建对应的解析器和风格字典的键为风格名称,值为该风格的规则,根据每种编程语言的特点,构建相应的风格字典。风格字典中的键是风格名称,而值则是对应风格的转换规则。例如,对于C语言,风格名称可以是“0.1”,其中小数点左边的数字“0”表示该风格所属的类型,即“修改变量名”,而小数点右边的数字“1”表示该类型的子风格,即“驼峰命名法”。这样就构建了一个风格规则:“0.1: (‘val’, ‘camel’)”,表示将变量名转换为驼峰命名法。风格字典就是由该语言设计的所有风格组成。再输入源代码,经过格式化代码之后,生成CST树,最后输入风格列表,例如[0.1, 1.1, 3.2],然后查询风格字典,得到所有风格的匹配规则和替换规则。

匹配模块:输入风格列表中所有风格的匹配规则,并初始化匹配节点列表为空,该列表存放所有匹配规则成功的节点。接着输入代码的CST树,递归遍历CST的每一个节点,如果该节点不符合匹配规则,如图中红色实线所示,则遍历该节点的所有子节点,如果符合匹配规则,则将其加入到匹配节点中,根据不同风格的匹配规则,可能继续嵌套的遍历子节点或者不继续往下遍历,直到遍历完所有CST节点,匹配阶段结束,最终输出匹配节点列表。

替换模块:输入风格列表中风格的替换规则以及匹配节点列表,对列表中的每一个节点,根据该风格对应的替换规则,得到修改操作(将在下一节中详细介绍),并将其加入到修改列表中,遍历完成后,根据修改列表,在源代码上直接进行修改,得到该风格转换后的代码。如果还有风格需要修改,则跳回到初始化模块,查询下一个风格的匹配规则和替换规则,重复上述的步骤,直到所有的风格都转换完毕,输出最终的修改后的代码,并检查语法,如果代码发生了改变并且语法正确,则转换成功,否则转换失败。
在这里插入图片描述

增删算法

tree-sitter编辑CST树的成本比较高,而且操作不方便,容易出错,因此考虑对源代码直接进行替换,而不是编辑CST树。在替换模块中,对源代码进行修改操作可以分为插入操作和删除操作两类,通过这两种操作的组合实现替换操作。

为了形式化定义这两种操作,假设原始代码为x,定义插入操作为 I(x, i, s),表示在字节偏移量为i处的右边插入字符串s,则插入操作如公式3-1所示,而删除操作定义为 D(x, i, j),表示在字节偏移量为i处,向左删除i个字节:

I ( x , i , s ) = x [ : i ] + s + x [ i + 1 : ] I(x,i,s)=x[:i]+s+x[i+1:] I(x,i,s)=x[:i]+s+x[i+1:]
D ( x , i , j ) = x [ : i − j ] + x [ i + 1 ] D\left(x,i,j\right)=x\left[:i-j\right]+x\left[i+1\right] D(x,i,j)=x[:ij]+x[i+1]

所有的修改都可以总结为这两种操作,如果是替换操作,假设想将第i到第j字节偏移的内容修改为字符串s,则替换操作S(x, i, j, s) 可以由删除操作和插入操作组合:
S ( x , i , j , s ) = I ( D ( x , j , j − i + 1 ) , i , s ) = x [ : i ] + s + x [ j + 1 : ] S\left(x,i,j,s\right)=I\left(D\left(x,j,j-i+1\right),i,s\right)=x\left[:i\right]+s+x\left[j+1:\right] S(x,i,j,s)=I(D(x,j,ji+1),i,s)=x[:i]+s+x[j+1:]

所有的修改操作合并组成修改列表O = [O1, O2, … ],其中Oi表示第i个操作,由于插入或删除会导致接下来的代码内容的字节偏移发生变化,而所有的修改操作都是在原始没有修改的代码上求得的字节偏移量,因此需要设计算法,来弥补每一次修改的字节偏移,并能够准确的对源代码进行修改,设计以下算法,如算法1增删算法所示,输入原始代码和修改列表,输出经过一系列增删操作后修改的代码。

首先需要对操作进行排序,按照修改位置从小到大排序,这样就不会有冲突,另外,由于修改后的偏移发生了变化,因此需要一个变量diff来记录这个偏移量,每一次修改都需要累加diff,例如在当前位置i插入了长度为d的字符串,则diff需要加上d,那么原先在i之后的操作,假设位于j,那么现在的偏移将变成j+diff,即j+d,删除操作diff需要减去删除的字节数,这样就能保证所有替换操作互相之间不会冲突。在算法当中,x[a:b]表示代码x字节偏移a到字节偏移b之间的子字符串,加法操作代表字符串的拼接。

在这里插入图片描述

tree-sitter介绍

在线生成CST树网址:https://tree-sitter.github.io/tree-sitter/playground
在这个网站中,可以解析指定语言,查看各个节点的属性和对应的代码,便于可视化解析
在这里插入图片描述

该库会对指定的语言生成解析器,并保存在build/下的.so文件中,后使用Language类加载该.so文件,得到对应语言的编译器,接着生成具体语法树CST,具体请参考change_program_style.py文件下的SCTS.__init__(self, langauge)方法。
CST节点的固有属性如下表所示
在这里插入图片描述
通过节点固有属性,我们可以获取到节点和源代码之间的相关性,例如行号、代码等,然而对于节点的操作不仅限于固有属性,还需要能够遍历CST树,获取节点与节点之间的关系,下表为CST节点的关系属性,通过获取节点的关系属性,可以按照不同顺序遍历。
在这里插入图片描述

项目目录结构说明

dataset目录下存放测试用的代码
c、cpp、java、python目录下存放各个语言的风格转换器,transform*.py文件中包含rec_*函数,用于匹配节点,cvt_*函数用于替换节点,config.py为配置文件

add_transform.py文件可以增加自定义的风格,需要自己写代码(参考transform*.py),执行python add_transform.py --l java --n type可以增加java语言type类风格的转换器

confuse.py可以混淆python代码,执行效果
原始代码如下

def can_add_ball(x, y): # 当前坐标是否覆盖了其他桌上的球
    for i in coord:
        if dot2dot(x, y, i[0], i[1]) < 2 * ball:
            return False
    return True

def balls(): # 添加球
    for _ in range(int(np.random.randint(8,15))):
        x, y = np.random.uniform(ball, length - ball), np.random.uniform(ball, width - ball)
        if can_add_ball(x, y):
            ax.add_patch(mpatches.Circle([x, y], ball, color='red'))
            coord.append([x, y])
    for color in ['blue', 'yellow', 'green', 'brown', 'black', 'pink']:
        x, y = np.random.uniform(ball, length - ball), np.random.uniform(ball, width - ball)
        if can_add_ball(x, y):
            ax.add_patch(mpatches.Circle([x, y], ball, color=color))
            coord.append([x, y])

混淆代码如下

def f_36(t_11, t_22): 
    for t_28 in t_42:
        if (f_38.__call__(t_11, t_22, t_28[((((14 % 5 + 3) * 2 - 10) // 4) - 1)], t_28[((5 * 4 - (6 + 2) + 9) // 20)]) < (((9 * (4 - 2) + 3) % 10)*2) * ball) and (sum(x for x in range((5*(((20 // 4) + 6 - (3 ** 2)) // 2)))) > (((4 * 3 - (5 + 1) + 8) // 10) - 1)):
            return not (all(type(x) is int for x in [((8 * (3 - 1) + 5) % 4), (((3 ** 3 // 9 - 5 + 4) // 2)*2), (((2 ** 3 + 10 // 2 - 9) % 3)*3), ((((20 // 4) + 6 - (3 ** 2)) // 2)*2*2)]))
    return (True if (((20 // 4) + 6 - (3 ** 2)) // 2)+(((3 * 4 - 6) // 2 + 5) // 8) == (((5 * 4 - (6 + 2) + 9) // 20)*2) else False) and (False if ((((15 % 4) ** 2 - 3) // 5)*2)*(2*(((5 * 3 - 10) // 2 + 1) // 3)) != (2*2*((10 * (3 - 1) + 5) % 4)) else True)

def f_10(): 
    for t_31 in range.__call__(int.__call__(np.random.randint.__call__((2*((2 ** 3 - 4 * 2 + 8) // 8)*2*2),(5*((10 * (3 - 1) + 5) % 4)*3)))):
        t_11, t_22 = np.random.uniform.__call__(ball, length - ball), np.random.uniform.__call__(ball, width - ball)
        if (f_36.__call__(t_11, t_22)) and (bool(dict(a=((2 ** 3 + 10 // 2 - 9) % 3), b=((((5 * 3 - 10) // 2 + 1) // 3)*2)))):
            ax.add_patch.__call__(mpatches.Circle.__call__([t_11, t_22], ball, t_12='\x72\x65\x64'))
            t_42.append.__call__([t_11, t_22])
    for t_12 in ['\x62\x6c\x75\x65', '\x79\x65\x6c\x6c\x6f\x77', '\x67\x72\x65\x65\x6e', '\x62\x72\x6f\x77\x6e', '\x62\x6c\x61\x63\x6b', '\x70\x69\x6e\x6b']:
        t_11, t_22 = np.random.uniform.__call__(ball, length - ball), np.random.uniform.__call__(ball, width - ball)
        if (f_36.__call__(t_11, t_22)) and (sum(x for x in range((((10 * (3 - 1) + 5) % 4)*5))) > (((10 * (3 - 1) + 5) % 4) - 1)):
            ax.add_patch.__call__(mpatches.Circle.__call__([t_11, t_22], ball, t_12=t_12))
            t_42.append.__call__([t_11, t_22])

风格列表

C/C++主要风格列表如下,具体请看源码注释
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

java主要风格列表
在这里插入图片描述
python主要风格列表
在这里插入图片描述
在这里插入图片描述

tree-sitter java 是一种使用 Tree-sitter 技术实现的 Java 语法解析工具。Tree-sitter一款高效、跨平台的解析器生成器,能够生成用于解析多种编程语言的解析器。通过使用 Tree-sitter java,我们可以进行 Java 代码的解析,实现语法高亮、代码导航、自动补全等功能。 Tree-sitter java 的工作原理是先使用 Tree-sitter 技术生成 Java 语言的解析器,并将解析器集成到我们的应用程序中。在解析过程中,Tree-sitter java 会将 Java 代码转换成一个抽象的语法树(AST),这个语法树可以准确地表示代码的各种语法结构和语义。我们可以通过遍历这棵语法树来分析代码,实现各种功能。 相比于传统的基于正则表达式或有限状态机的解析器,Tree-sitter java 的优势在于它是基于语法树的。语法树可以准确地表示代码的结构和语义,使得我们可以更加灵活地分析和操作代码。而且,Tree-sitter java 的解析过程是非常快速的,可以快速地处理大型的 Java 代码库。 使用 Tree-sitter java 可以带来很多好处。首先,它可以为我们的代码编辑器提供丰富的语法高亮功能,使得代码更加易读。其次,我们可以利用它实现更智能的代码导航和自动补全功能,提高我们的开发效率。此外,Tree-sitter java 还可以用于代码分析和重构,帮助我们理解和改进代码质量。 总之,Tree-sitter java 是一种强大的 Java 语法解析工具,通过利用它,我们可以实现更智能、更高效代码编辑和分析。它不仅提高了我们的开发效率,还有助于改善我们的代码质量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

rebibabo

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

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

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

打赏作者

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

抵扣说明:

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

余额充值