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中。