用python做java语言的静态分析

7 篇文章 0 订阅

最近在对java语言做静态分析,发现javalang这个好用的python包。特别记录一下。
我们可以把javalang解析出的语法树进行再加工,生成我们需要的元信息,再结合校验规则,做一些常见编码问题的分析。
这就是lint类工具的一般性原理。

语法树打印

javalang的顶层结构叫CompilationUnit,就是一颗语法树。但直接打印出来,没有层级结构,很难看。尝试用json.dumps转为json,报错:

Object of type PackageDeclaration is not JSON serializable

需要为json.dumps指定default方法:

fp.write(json.dumps(cu, indent=2, default=node2dict))

node2dict方法定义如下:

def node2dict(obj):
    if isinstance(obj, Node):
        return obj.__dict__
    elif isinstance(obj, set):
        return list(obj)
    else:
        return obj

这样就可以打印出比较好看的语法树结构了。

不支持的java语法特性

python的javalang包对java的方法引用(method reference)还没完全支持,像下面写法:

Arrays.stream(ids).filter(myFilter::contains).toArray(Long[]::new);

解析会报错,形如:

Expected '.' at Keyword "new" line xx, position xx

可改写为:

Arrays.stream(ids).filter(myFilter::contains).toArray(sz -> new Long[sz]);

临时规避之。
也可以做一个Parser的派生类,修复该问题:

class ParserEx(Parser):
    def parse_identifier_suffix(self):
        if self.try_accept('[', ']'):
            array_dimension = [None] + self.parse_array_dimension()
            if self.try_accept('.', 'class'):
                return tree.ClassReference(type=tree.Type(dimensions=array_dimension))
            # patch: support Long[]::new syntax
            return tree.MemberReference()
        else:
            return super().parse_identifier_suffix()

竞品对比

还有一个javac-parser包,该包会启动JVM实例,并解析传入的java源码,但它只输出lex符号,而不会输出语法树,这点不满足我们的要求。

用途

我是用javalang来做java源码安全性方面的静态扫描。举两个例子:
1、若要排查redos攻击,可通过遍历javalang语法树,找到所有调用Pattern.compile、Pattern.matches或xxx.matches的MethodInvocation节点,取出其第一个参数,如果该参数为Literal,直接调用redosHunter工具判断是否有redos风险,否则尝试分析其是否为常量还是变量,再做进一步处理。
2、若要排查日志打印隐私风险,可在javalang语法树上找到类似LOGGER.info、LOGGER.warn这样的MethodInvocation节点,设法找到其参数所涉及的变量,如果变量是基本类型,根据变量名判断是否可能是隐私字段;如果变量是data类,则找出其数据成员中是否包含隐私字段。

界面

涉及推广,需要做一个界面。python的GUI开发有如下选择:
tk
wxPython
pyQt/pySide
pyQt和pySide是很类似的,就是协议不同,后者是LGPL的。QT系因为有QtDesigner工具自动生成代码,生成的界面也比较漂亮,再加上协议的考量,最后优选pySide6。
QT的核心概念是signal和slot,分别对应MFC里的事件消息和事件响应函数。在做多线程应用时,同样要注意:UI控件的操作只能在主线程里进行,如要在子线程里修改UI,须发送signal到主线程,在主线程的slot函数里修改UI。
另外,QT里的子线程不要用python自带的Thread类,而要用QT的QThread,这样才能在子线程里使用signal。并且,我们要让QThread作为类成员常驻,而非局部变量,否则会报错:

QThread:Destroyed while thread is still running
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值