在网上转了一圈,好像对此包的使用分析文章太少,所以引出此文做一个介绍。
在python3环境中安装pycparser。
如果要处理C语言代码中的#include
或者#define
语句,需要gcc或者clang的支持(当然可以忽略掉,不用纠结编译上的问题啦)。
个人使用的是MinGW,印象中LLVM也可以,但会出一些问题,所以我选择前者。
我只推荐用来处理#define
语句,编译器会根据#define
的内容替换掉代码 (处理#include
语句,会引入include文件中很多不相关的抽象语法树信息)
导入pycparser
# parser_file 用于处理c语言文件
from pycparser import parse_file
# c语言有错误时,会引出此错误
from pycparser.plyparser import ParseError
# c_ast.py 文件下包含了抽象语法树的节点类
from pycparser.c_ast import *
有细心的朋友会发现pycparser的_c_ast.cfg 文件有关文件的详细描述,对每种节点类做了分析,个人对节点属性做了总结:
''' pycparser定义的一些规则:
dim: the dimension
dim_quals: list of dimension qualifiers
op: =, +=, /= etc.
type: the typename
init: InitList for the initializer list
type: int, char, float, etc. see CLexer for constant token types
name: the variable being declared
quals: list of qualifiers (const, volatile)
funcspec: list function specifiers (i.e. inline in C99)
storage: list of storage specifiers (extern, register, etc.)
type: declaration type (probably nested with all the modifiers)
init: initialization value, or None
bitsize: bit field size, or None'''
这些属性信息方便我们对节点做操作。
获取c语言文件的抽象语法树ast,如果要处理#include
语句,需要下载fake_libc_include文件夹,让编译器预处理常用的方法(添加其到代码的抽象语法树中)
# fake_libc_include文件夹放在处理的c语言目录下
ast = parse_file(filename, use_cpp = True, cpp_path=r'C:\MinGW\bin\gcc.exe', cpp_args=['-E', r'-Iutils/fake_libc_include'])
# 展示ast结构(官方的方法)
ast.show()
# 打印ast节点及其属性
print(ast)
parser_file()
方法也可以设置use_cpp=False,不用本地的c语言编译器预处理代码,就能输出抽象语法树。
with open(filename, encoding='utf-8') as f:
txt = f.read()
ast = c_parser.CParser().parse(txt)
分析代码的时候,可以对每种节点单独做分析,也可以用统一的方法直接处理,接下来举个例子
def get_tree_node(node):
nodeType = type(node)
if nodeType is ArrayDecl:
# ArrayDecl 是给定类型数组的嵌套声明(int a[1][2] 外层是1维度的ArrayDecl,嵌套2维的ArrayDecl)
# type = Node (数组节点的类型,int a[1][2] 外层type = ArrayDecl,内部type = int)
# dim = Node (数组维度的表示,Constant/ ID)
# dim_quals = [str] (数组维度的限定符号,C99允许在方法声明中加上: static, const;void f(int a[const]){}//成立)
pass
elif nodeType is ArrayRef:
# ArrayRef 是对以声明的数组中某一个位置的值做操作(a[0][0] = 1;)
# name = Node (操作的数组名称)
# subscript = Node (数组的位置,通常是 Constant)
pass
elif nodeType is Assignment:
# Assignment 赋值语句
# op = str (操作符: =, +=, -=,...)
# lvalue = Node (被赋予的对象)
# rvalue = Node (赋予的值)
pass
elif nodeType is BinaryOp:
# BinaryOp 二元操作符
# op = str (操作符)
# left = Node (表达式左侧)
# right = Node (表达式右侧)
pass
elif nodeType is Break:
# Break 语句,节点无内容
pass
elif nodeType is Case:
# Case 条件判断语句
# expr = Node (条件表达式)
# stmts = [Node] (语句块)
pass
elif nodeType is Cast:
# Cast 数据类型转换
# to_type = Node (转换后的类型)
# expr = Node (需要转换数据类型的表达式)
pass
elif nodeType is Compound:
# Compound 复合语句,在C99中表示块项的列表(包含decls或者stmts)
# block_items = [Node] (语句列表)
pass
elif nodeType is CompoundLiteral:
# CompundLiteral 复合字面量,C99中构造指定类型的匿名对象 ( type ) { initializer-list }
# https://zh.cppreference.com/w/c/language/compound_literal
# type = Node (复合字面量的类型)
# init = Node (复合字面量的初始化列表)
pass
elif nodeType is Constant:
# Constant 常量,类型: int, char, float, string, ...
# type = str (值类型)
# value = str (具体值)
pass
elif nodeType is Continue:
# Continue 语句,无节点内容
pass
elif nodeType is Decl:
# declaration 声明节点
# name = str (被声明的变量)
# quals = [str] (限定符号列表: const, volatile)
# storage = [str] (存储说明符列表: extern, register, etc.)
# funcspec = [str] (函数说明符列表: C99的inline)
# type = Node (声明节点的类型,可能与修饰符嵌套)
# init = Node (初始化值,或者为None)
# bitsize = Node (位域bit field大小,或者为None)
pass
elif nodeType is DeclList:
# DeclList for循环的第一个表达式用此节点表示(for(int i,j;;){},decls会有两个Decl)
# decls = [Node] (声明表达式列表)
pass
elif nodeType is Default:
# Default switch对应的语句
# stmts = [Node] (语句列表)
pass
elif nodeType is DoWhile:
# DoWhile dowhile条件代码块
# cond = Node (条件语句)
# stmt = Node (代码块中的语句,一般是Compound节点)
pass
elif nodeType is EllipsisParam:
# EllipsisParam C语言函数中的可变参数声明, int printf(char *input,...)
# 无节点内容
pass
elif nodeType is EmptyStatement:
# EmptyStatement 空语句,单独一个;符号
# 无节点内容
pass
elif nodeType is Enum:
# Enum 枚举类型说明符(enum e{a = 1},Enum节点代表'enum e')
# name = str (枚举的名称)
# values = Node (枚举中列举的值,通常是EnumeratorList)
pass
elif nodeType is Enumerator:
# Enumerator 具体的枚举值(enum e{a = 1,b},Enumerator节点代表'a = 1','b')
# name = str (枚举中具体的名称)
# value = Node (枚举具体值,可以为None)
pass
elif nodeType is EnumeratorList:
# EnumeratorList 是Enum的子节点,Enumerator的父节点,用于列举多个枚举值
# enumerators = [Node] (列出多个Enumerator节点)
pass
elif nodeType is ExprList:
# ExprList 逗号分隔符的表达式列表
# exprs = [Node] (逗号分隔的多个表达式,比如for(;;ExprList))
pass
elif nodeType is FileAST:
# 读取文件的第一个节点
# ext = [Node] (列表有三种类型: Decl, Typedef, FuncDef)
pass
elif nodeType is For:
# For 循环 for (init; cond; next) stmt
# init = Node
# cond = Node
# next = Node
# stmt = Node (这里不是语句列表,是节点)
pass
elif nodeType is FuncCall:
# FuncCall 方法调用
# name = Node (ID)
# args = Node (ExprList)
pass
elif nodeType is FuncDecl:
# FuncDecl 方法声明type (args) 有两种:一种在方法定义FuncDef节点后出现此节点;
# 另一种是在当前方法中调用调用另外一种的、可能出现在任何位置的方法,调用前声明它的存在
# 例子:第三个代码段 -> https://zh.cppreference.com/w/c/language/functions
# type = Node (一般是TypeDecl,不仅有type,还有名称)
# args = Node (方法的参数)
pass
elif nodeType is FuncDef:
# FuncDef 方法定义,不同于第二种FuncDecl,有具体的函数实现过程
# decl = Node (一般是包含FuncDecl的Decl节点,方法的声明)
# param_decls = [Node]或None (一般为None,除非是K&R风格的C语言声明样式,https://stackoverflow.com/questions/3092006/function-declaration-kr-vs-ansi)
# body = Node (函数实现的代码块)
pass
elif nodeType is Goto:
# Goto 跳转语句,只有一个名称
# name = str
pass
elif nodeType is ID:
# ID 名称节点
# name = str (变量之类的节点会用ID的name表示出具体名称)
pass
elif nodeType is IdentifierType:
# IdentifierType 简单标识符,比如void, char, typedef定义之类
# name = [str] (标识符字符串列表)
pass
elif nodeType is If:
# If if条件语句,包含了条件成立和条件不成立时执行的内容
# cond = Node (条件语句)
# iftrue = Node (条件成立的代码块,一般是Compound)
# iffalse = Node (条件不成立)
pass
elif nodeType is InitList:
# InitList 用于复合字面值Compound Literal的初始化列表 ( type ) { initializer-list }
# exprs = [Node] (初始化列表的具体数值,一般是Constant)
pass
elif nodeType is Label:
# Label Goto对应的标签
# name = str (标签的名称)
# stmt = Node (标签当前语句)
pass
elif nodeType is NamedInitializer:
# NamedInitializer C99的初始化 指派符列表 = 初始化器 -> https://zh.cppreference.com/w/c/language/initialization
# 例如:struct {int a[3], b;} w[] = {[0].a = {1}, [1].a[0] = 2}; 中,[0].a = {1} 和 [1].a[2] = 2
# name = [Node] (上述初始化器,数组索引是Constant,结构体变量索引是ID)
# expr = Node (初始化器 ‘=’符号右侧表达式)
pass
elif nodeType is ParamList:
# ParamList 逗号分隔符的函数参数声明
# params = [Node] (参数列表)
pass
elif nodeType is PtrDecl:
# PtrDecl 指针
# quals = [str] (限定符)
# type = Node (指针的名称以及类型信息)
pass
elif nodeType is Return:
# Return 返回语句
# expr = Node (表达式)
pass
elif nodeType is Struct:
# Struct 结构体语句
# name = str (结构体标识名称)
# decls = [Node] (声明的内容)
pass
elif nodeType is StructRef:
# StructRef 结构体引用节点 struct a{int b}; a->b = 0;
# name = Node (名称,如a)
# type = str (结构体引用方式,如-> 还有.)
# field = Node (变量b)
pass
elif nodeType is Switch:
# Switch 条件判断语句
# cond = Node (条件)
# stmt = Node (语句块节点,通常是Compound)
pass
elif nodeType is TernaryOp:
# TernaryOp 三元表达式 cond ? iftrue : iffalse
# cond = Node (条件)
# iftrue = Node (条件正确的语句)
# iffalse = Node
pass
elif nodeType is TypeDecl:
# TypeDecl 类型声明 quals type declname
# quals = [str] (限定符)
# type = Node (类型)
# declname = str (名称)
pass
elif nodeType is Typedef:
# Typedef 类型定义,和Decl类似,但没有其节点的部分属性
# storage = [str] (存储说明符号列表)
# quals = [str] (限定符)
# name = str (typedef内容的名称)
# type = Node (typedef其中的具体语句)
pass
elif nodeType is Typename:
# 未知何时出现
# Typename 在官方代码中解释是说明符号列表。
# name = str
# quals = [str]
# type = Node
pass
elif nodeType is UnaryOp:
# UnaryOp 一元操作符
# op = str (操作符)
# expr = Node (表达式)
pass
elif nodeType is Union:
# Union 共用体
# name = str (名称)
# decls = [Node] (共用体成员)
pass
elif nodeType is While:
# While 循环语句
# cond = Node (条件语句)
# stmt = Node (表达式)
pass
elif nodeType is Pragma:
# Pragma C语言预处理 #pragma string -> https://zh.cppreference.com/w/cpp/preprocessor/impl
# string = str (定义的行为控制)
pass
python环境做C语言分析-pycparser的使用方法(2) 后来发现还可以根据节点内部的child属性做分析,但是该方法不算太好,属性为None的节点会自动过滤,所以需要适当做选择。
可以根据AST节点,还原代码控制流图,写出变量的define和use图像:
CFG-dupath-of-C
// 归并排序代码
#include
#define MAXSIZE 10
void merging(int *list1, int list1_size, int *list2, int list2_size)
{
int i,j,k, m;
int temp[MAXSIZE];
i = j = k = 0;
while(i < list1_size && j < list2_size)
{
if(list1[i] < list2[j])
{
temp[k] = list1[i];
k++;
i++;
}
else
{
temp[k++] = list2[j++];
}
}
while(i < list1_size)
{
temp[k++] = list1[i++];
}
while(j < list2_size)
{
temp[k++] = list2[j++];
}
for(m = 0;m < (list1_size + list2_size);m++)
{
list1[m] = temp[m];
}
}
void MergeSort(int k[], int n)
{
if(n > 1)
{
int *list1 = k;
int list1_size = n/2;
int *list2 = k + list1_size;
int list2_size = n - list1_size;
MergeSort(list1, list1_size);
MergeSort(list2, list2_size);
merging(list1, list1_size, list2, list2_size);
}
}
int main()
{
int i, a[10] = {5, 2, 6, 0, 3, 9, 1, 7, 4, 8};
MergeSort(a, 10);
for(i = 0;i < 10;i++)
{
printf("%d", a[i]);
}
printf("\n\n");
return 0;
}
小更新,python环境做C语言分析-pycparser的使用方法(2),采用更简单的方法对ast节点做遍历操作,也可以统一处理节点属性,相比此方法更方便(缺点是某些属性为None时,不会显示出该属性名称,会麻烦很多)