Skip to content

P3: 从上而下全局语义分析

这一阶段进行从入口函数开始,对整个程序进行全局程序分析,其粒度与上一阶段从下而上是一致的,但更具有优势,有的无法确定为anything的状态将被大量替换为concrete实际的状态,进一步增强程序的语义分析结果的准确性。

总体流程

这一阶段以入口函数为单位,在函数帧(frame)的基础上维护一个函数帧栈(stack),进行全局语义分析。其具体步骤如下:

输入:入口函数entry_method、函数帧栈frame_stack  
输出:全局语义分析结果

// 初始化:将入口函数帧压入栈
frame = create_frame(entry_method)
frame_stack.push(frame)

while frame_stack ≠ ∅:
    frame = frame_stack.peek()

    // 检查该帧是否有待分析的callee(来自上一次中断)
    if frame.pending_callees ≠ ∅:
        // 取出一个待分析的callee(深度优先)
        // 先去分析 callee
        callee = frame.pending_callees.pop()
        callee_frame = create_frame(callee)
        frame_stack.push(callee_frame)
        continue  

    // 否则,尝试分析当前帧
    results = analyze_method(frame)

    if results.success:
        // 分析完成,弹出
        frame_stack.pop()

    elif results.interruptions is not empty:
        // 遇到新的未解析调用,记录为pending_callees
        frame.pending_callees = results.interruptions 
        // 不弹出当前帧!留在栈顶,等callees分析完再回来

    else:
        // 无法继续(如外部调用),强制弹出
        frame_stack.pop()

复用从下而上的分析逻辑

对函数的语义分析,复用从下而上的逻辑。唯一区别的主要有以下几个部分:

首先,在compute_stmt_state()中,从下而上的分析会将外部符号的状态假设为anything,而全局语义分析中,外部符号的状态将根据函数帧栈frame stack,逆向进行推导和追踪,直到找到确定的状态。这部分区别体现在core/global_semantics.py中。

其次,在对每条语句的指针关系的分析中,call_stmt的处理也有差异。在从下而上的分析中,call_stmt的指针关系会应用函数摘要;在全局语义分析中,call_stmt的指针关系会进入callee函数,连接调用语句的实参和callee函数的形参,当callee函数完毕后,也会去应用返回值。这部分的差异体现在core/global_stmt_states.py中。