令$Calls$表示CFG中所有函数调用节点的集合。基于调用字符串的方法,定义上下文敏感信息为:
$$ Contexts = Calls^{\leq ~k} $$
其中k是一个正整数。通过选择这种调用上下文,我们可以获得类似于函数克隆或内联的效果,但实际上不改变CFG。
这个想法是用一个元组$(c1, c2,\dots,c_m) \in Calls^{\leq k}$ 标识调用栈上的前m个调用点。如果$(e_1,\dots,e_n) \in (Contexts \rightarrow States)^n$ 是一个格元素,那么$e_i(c_1, c_2,\dots, c_m)$ 提供了一个抽象状态,用于近似表示可能出现在第i个CFG节点的运行时状态;假设包含该节点的函数是从c1调用的,而包含c1的函数是从c2调用的,依此类推。元组的长度被限制,以确保$Contexts$是有限的。由空元组表示的上下文,记为 $\epsilon$,因此在主函数中启动执行时标识为空的调用栈。元组 $(c_1, c_2,\dots, c_m)$,其中m < k,标识高度为m的调用栈(在这种情况下,$c_m$必须是主函数中的调用节点);而m = k的元组标识至少为m的调用栈高度(直观地说,长度大于k的调用字符串会被截断)。
分析的最坏情况复杂性显然受到k的选择影响。
为了演示调用字符串方法,我们再次考虑应用于第8.2节中的程序的符号分析。让c1和c2分别表示程序中主函数中的两个调用节点。为简单起见,我们将关注k = 1的情况,这意味着Contexts = { $\\epsilon$ , c1, c2},因此分析只跟踪最顶层的调用站点。现在我们可以定义分析约束,以便在函数f的入口处获得以下格元素:
这个格元素对于不同的调用者具有不同的抽象值,具体取决于调用者。请注意, $\epsilon$ 上下文的信息是不可达的,因为f不是主函数,而是始终从c1或c2执行的。
在函数调用时的参数传递方式与无上下文分析相同,但现在需要考虑调用上下文。假设w是一个函数调用节点,v是被调用的函数$f(b_1,\\dots,b_n)$的入口节点。那么对于抽象状态 $s_w^{c'}$ 定义如下:
这个定义描述了从w到v的数据流,类似于无上下文的变体,但现在使用了调用节点的上下文c’作为参数。这个定义还模拟了如果该节点和上下文的组合是不可达的话(可能是因为分析尚未遇到该节点和上下文的任何数据流),那么从调用节点w中不会出现新的数据流。
对于函数入口节点v的约束规则可以写成如下形式:
其中$w \in pred(v)$,c’是上下文集合中的一元。简言之,对于调用节点w处的任何调用上下文c’,通过计算函数参数构建一个抽象状态$s_w^{c'}$,然后传播到函数入口节点v处的调用上下文c。在这种k = 1的简单情况下,调用节点w直接用作函数入口节点的上下文c,但对于较大的k值,需要表达调用站点如何被推送到堆栈中(由调用上下文的调用字符串表示)。
或者可以将约束规则表示为一个方程,通过收集所有相关调用节点和上下文的抽象状态来实现:
与上下文无关的变体相比,v处的抽象状态现在是由上下文c参数化的,并且我们只包含与c匹配的调用节点的信息。
假设v是一个保存返回值的after-call节点,该返回值存储在变量X中,$v'$ 是相关的call节点,w是pred(v)中的函数退出节点。节点v的约束规则合并了来自 $v'$ 的抽象状态和来自w的返回值,现在考虑了调用上下文和可达性:
请注意,使用这种上下文敏感性,$v'$ 既是一个调用节点又是一个调用上下文,而result的抽象值是从调用上下文 $v'$ 中的退出节点w中获取的。